OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 library browser; | 4 library browser; |
5 | 5 |
6 import "dart:async"; | 6 import "dart:async"; |
7 import "dart:convert" show LineSplitter, UTF8, JSON; | 7 import "dart:convert" show LineSplitter, UTF8, JSON; |
8 import "dart:core"; | 8 import "dart:core"; |
9 import "dart:io"; | 9 import "dart:io"; |
10 import "dart:math" show max, min; | 10 import "dart:math" show max, min; |
11 | 11 |
12 import 'android.dart'; | 12 import 'android.dart'; |
13 import 'http_server.dart'; | 13 import 'http_server.dart'; |
14 import 'path.dart'; | 14 import 'path.dart'; |
15 import 'utils.dart'; | 15 import 'utils.dart'; |
16 | 16 |
| 17 import 'reset_safari.dart' show |
| 18 killAndResetSafari; |
| 19 |
17 class BrowserOutput { | 20 class BrowserOutput { |
18 final StringBuffer stdout = new StringBuffer(); | 21 final StringBuffer stdout = new StringBuffer(); |
19 final StringBuffer stderr = new StringBuffer(); | 22 final StringBuffer stderr = new StringBuffer(); |
20 final StringBuffer eventLog = new StringBuffer(); | 23 final StringBuffer eventLog = new StringBuffer(); |
21 } | 24 } |
22 | 25 |
23 /** Class describing the interface for communicating with browsers. */ | 26 /** Class describing the interface for communicating with browsers. */ |
24 abstract class Browser { | 27 abstract class Browser { |
25 BrowserOutput _allBrowserOutput = new BrowserOutput(); | 28 BrowserOutput _allBrowserOutput = new BrowserOutput(); |
26 BrowserOutput _testBrowserOutput = new BrowserOutput(); | 29 BrowserOutput _testBrowserOutput = new BrowserOutput(); |
(...skipping 19 matching lines...) Expand all Loading... |
46 Process process; | 49 Process process; |
47 | 50 |
48 Function logger; | 51 Function logger; |
49 | 52 |
50 /** | 53 /** |
51 * Id of the browser | 54 * Id of the browser |
52 */ | 55 */ |
53 String id; | 56 String id; |
54 | 57 |
55 /** | 58 /** |
56 * Delete the browser specific caches on startup. | 59 * Reset the browser to a known configuration on start-up. |
57 * Browser specific implementations are free to ignore this. | 60 * Browser specific implementations are free to ignore this. |
58 */ | 61 */ |
59 static bool deleteCache = false; | 62 static bool resetBrowserConfiguration = false; |
60 | 63 |
61 /** Print everything (stdout, stderr, usageLog) whenever we add to it */ | 64 /** Print everything (stdout, stderr, usageLog) whenever we add to it */ |
62 bool debugPrint = false; | 65 bool debugPrint = false; |
63 | 66 |
64 // This future returns when the process exits. It is also the return value | 67 // This future returns when the process exits. It is also the return value |
65 // of close() | 68 // of close() |
66 Future done; | 69 Future done; |
67 | 70 |
68 Browser(); | 71 Browser(); |
69 | 72 |
(...skipping 28 matching lines...) Expand all Loading... |
98 'ie10', | 101 'ie10', |
99 'ie11', | 102 'ie11', |
100 'dartium' | 103 'dartium' |
101 ]; | 104 ]; |
102 | 105 |
103 static const List<String> BROWSERS_WITH_WINDOW_SUPPORT = const [ | 106 static const List<String> BROWSERS_WITH_WINDOW_SUPPORT = const [ |
104 'ie11', | 107 'ie11', |
105 'ie10' | 108 'ie10' |
106 ]; | 109 ]; |
107 | 110 |
| 111 /// If [browserName] doesn't support Window.open, we use iframes instead. |
| 112 static bool requiresIframe(String browserName) { |
| 113 return !BROWSERS_WITH_WINDOW_SUPPORT.contains(browserName); |
| 114 } |
| 115 |
| 116 static bool requiresFocus(String browserName) { |
| 117 return browserName == "safari"; |
| 118 } |
| 119 |
108 // TODO(kustermann): add standard support for chrome on android | 120 // TODO(kustermann): add standard support for chrome on android |
109 static bool supportedBrowser(String name) { | 121 static bool supportedBrowser(String name) { |
110 return SUPPORTED_BROWSERS.contains(name); | 122 return SUPPORTED_BROWSERS.contains(name); |
111 } | 123 } |
112 | 124 |
113 void _logEvent(String event) { | 125 void _logEvent(String event) { |
114 String toLog = "$this ($id) - $event \n"; | 126 String toLog = "$this ($id) - $event \n"; |
115 if (debugPrint) print("usageLog: $toLog"); | 127 if (debugPrint) print("usageLog: $toLog"); |
116 if (logger != null) logger(toLog); | 128 if (logger != null) logger(toLog); |
117 | 129 |
(...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
262 * Add useful info about the browser to the _testBrowserOutput.stdout, | 274 * Add useful info about the browser to the _testBrowserOutput.stdout, |
263 * where it will be reported for failing tests. Used to report which | 275 * where it will be reported for failing tests. Used to report which |
264 * android device a failing test is running on. | 276 * android device a failing test is running on. |
265 */ | 277 */ |
266 void logBrowserInfoToTestBrowserOutput() {} | 278 void logBrowserInfoToTestBrowserOutput() {} |
267 | 279 |
268 String toString(); | 280 String toString(); |
269 | 281 |
270 /** Starts the browser loading the given url */ | 282 /** Starts the browser loading the given url */ |
271 Future<bool> start(String url); | 283 Future<bool> start(String url); |
| 284 |
| 285 /// Called when the driver page is requested, that is, when the browser first |
| 286 /// contacts the test server. At this time, it's safe to assume that the |
| 287 /// browser process has started and opened its first window. |
| 288 /// |
| 289 /// This is used by [Safari] to ensure the browser window has focus. |
| 290 Future<Null> onDriverPageRequested() => new Future<Null>.value(); |
272 } | 291 } |
273 | 292 |
274 class Safari extends Browser { | 293 class Safari extends Browser { |
275 /** | 294 /** |
276 * We get the safari version by parsing a version file | 295 * We get the safari version by parsing a version file |
277 */ | 296 */ |
278 static const String versionFile = | 297 static const String versionFile = |
279 "/Applications/Safari.app/Contents/version.plist"; | 298 "/Applications/Safari.app/Contents/version.plist"; |
280 | 299 |
281 /** | 300 static const String safariBundleLocation = "/Applications/Safari.app/"; |
282 * Directories where safari stores state. We delete these if the deleteCache | |
283 * is set | |
284 */ | |
285 static const List<String> CACHE_DIRECTORIES = const [ | |
286 "Library/Caches/com.apple.Safari", | |
287 "Library/Safari", | |
288 "Library/Saved Application State/com.apple.Safari.savedState", | |
289 "Library/Caches/Metadata/Safari" | |
290 ]; | |
291 | 301 |
292 Future<bool> allowPopUps() { | 302 // Clears the cache if the static resetBrowserConfiguration flag is set. |
293 var command = "defaults"; | 303 // Returns false if the command to actually clear the cache did not complete. |
294 var args = [ | 304 Future<bool> resetConfiguration() async { |
295 "write", | 305 if (!Browser.resetBrowserConfiguration) return true; |
296 "com.apple.safari", | 306 |
297 "com.apple.Safari.ContentPageGroupIdentifier." | 307 Completer completer = new Completer(); |
298 "WebKit2JavaScriptCanOpenWindowsAutomatically", | 308 handleUncaughtError(error, StackTrace stackTrace) { |
299 "1" | 309 if (!completer.isCompleted) { |
300 ]; | 310 completer.completeError(error, stackTrace); |
301 return Process.run(command, args).then((result) { | 311 } else { |
302 if (result.exitCode != 0) { | 312 throw new AsyncError(error, stackTrace); |
303 _logEvent("Could not disable pop-up blocking for safari"); | |
304 return false; | |
305 } | 313 } |
| 314 } |
| 315 Zone parent = Zone.current; |
| 316 ZoneSpecification specification = new ZoneSpecification( |
| 317 print: (Zone self, ZoneDelegate delegate, Zone zone, String line) { |
| 318 delegate.run(parent, () { |
| 319 _logEvent(line); |
| 320 }); |
| 321 }); |
| 322 Future zoneWrapper() { |
| 323 Uri safariUri = Uri.base.resolve(safariBundleLocation); |
| 324 return new Future(() => killAndResetSafari(bundle: safariUri)) |
| 325 .then(completer.complete); |
| 326 } |
| 327 |
| 328 // We run killAndResetSafari in a Zone as opposed to running an external |
| 329 // process. The Zone allows us to collect its output, and protect the rest |
| 330 // of the test infrastructure against errors in it. |
| 331 runZoned( |
| 332 zoneWrapper, zoneSpecification: specification, |
| 333 onError: handleUncaughtError); |
| 334 |
| 335 try { |
| 336 await completer.future; |
306 return true; | 337 return true; |
307 }); | 338 } catch (error, st) { |
308 } | 339 _logEvent("Unable to reset Safari: $error$st"); |
309 | 340 return false; |
310 Future<bool> deleteIfExists(Iterator<String> paths) { | 341 } |
311 if (!paths.moveNext()) return new Future.value(true); | |
312 Directory directory = new Directory(paths.current); | |
313 return directory.exists().then((exists) { | |
314 if (exists) { | |
315 _logEvent("Deleting ${paths.current}"); | |
316 return directory | |
317 .delete(recursive: true) | |
318 .then((_) => deleteIfExists(paths)) | |
319 .catchError((error) { | |
320 _logEvent("Failure trying to delete ${paths.current}: $error"); | |
321 return false; | |
322 }); | |
323 } else { | |
324 _logEvent("${paths.current} is not present"); | |
325 return deleteIfExists(paths); | |
326 } | |
327 }); | |
328 } | |
329 | |
330 // Clears the cache if the static deleteCache flag is set. | |
331 // Returns false if the command to actually clear the cache did not complete. | |
332 Future<bool> clearCache() { | |
333 if (!Browser.deleteCache) return new Future.value(true); | |
334 var home = Platform.environment['HOME']; | |
335 Iterator iterator = CACHE_DIRECTORIES.map((s) => "$home/$s").iterator; | |
336 return deleteIfExists(iterator); | |
337 } | 342 } |
338 | 343 |
339 Future<String> getVersion() { | 344 Future<String> getVersion() { |
340 /** | 345 /** |
341 * Example of the file: | 346 * Example of the file: |
342 * <?xml version="1.0" encoding="UTF-8"?> | 347 * <?xml version="1.0" encoding="UTF-8"?> |
343 * <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.co
m/DTDs/PropertyList-1.0.dtd"> | 348 * <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.co
m/DTDs/PropertyList-1.0.dtd"> |
344 * <plist version="1.0"> | 349 * <plist version="1.0"> |
345 * <dict> | 350 * <dict> |
346 * <key>BuildVersion</key> | 351 * <key>BuildVersion</key> |
(...skipping 15 matching lines...) Expand all Loading... |
362 for (var line in content) { | 367 for (var line in content) { |
363 if (versionOnNextLine) return line; | 368 if (versionOnNextLine) return line; |
364 if (line.contains("CFBundleShortVersionString")) { | 369 if (line.contains("CFBundleShortVersionString")) { |
365 versionOnNextLine = true; | 370 versionOnNextLine = true; |
366 } | 371 } |
367 } | 372 } |
368 return null; | 373 return null; |
369 }); | 374 }); |
370 } | 375 } |
371 | 376 |
372 void _createLaunchHTML(var path, var url) { | 377 Future<Null> _createLaunchHTML(var path, var url) async { |
373 var file = new File("${path}/launch.html"); | 378 var file = new File("${path}/launch.html"); |
374 var randomFile = file.openSync(mode: FileMode.WRITE); | 379 var randomFile = await file.open(mode: FileMode.WRITE); |
375 var content = '<script language="JavaScript">location = "$url"</script>'; | 380 var content = '<script language="JavaScript">location = "$url"</script>'; |
376 randomFile.writeStringSync(content); | 381 await randomFile.writeString(content); |
377 randomFile.close(); | 382 await randomFile.close(); |
378 } | 383 } |
379 | 384 |
380 Future<bool> start(String url) { | 385 Future<bool> start(String url) async { |
381 _logEvent("Starting Safari browser on: $url"); | 386 _logEvent("Starting Safari browser on: $url"); |
382 return allowPopUps().then((success) { | 387 if (!await resetConfiguration()) { |
383 if (!success) { | 388 _logEvent("Could not clear cache"); |
384 return false; | 389 return false; |
385 } | 390 } |
386 return clearCache().then((cleared) { | 391 String version; |
387 if (!cleared) { | 392 try { |
388 _logEvent("Could not clear cache"); | 393 version = await getVersion(); |
389 return false; | 394 } catch (error) { |
390 } | 395 _logEvent("Running $_binary --version failed with $error"); |
391 // Get the version and log that. | 396 return false; |
392 return getVersion().then((version) { | 397 } |
393 _logEvent("Got version: $version"); | 398 _logEvent("Got version: $version"); |
394 return Directory.systemTemp.createTemp().then((userDir) { | 399 Directory userDir; |
395 _cleanup = () { | 400 try { |
396 userDir.deleteSync(recursive: true); | 401 userDir = await Directory.systemTemp.createTemp(); |
397 }; | 402 } catch (error) { |
398 _createLaunchHTML(userDir.path, url); | 403 _logEvent("Error creating temporary directory: $error"); |
399 var args = ["${userDir.path}/launch.html"]; | 404 return false; |
400 return startBrowserProcess(_binary, args); | 405 } |
401 }); | 406 _cleanup = () { |
402 }).catchError((error) { | 407 userDir.deleteSync(recursive: true); |
403 _logEvent("Running $_binary --version failed with $error"); | 408 }; |
404 return false; | 409 try { |
405 }); | 410 await _createLaunchHTML(userDir.path, url); |
406 }); | 411 } catch (error) { |
407 }); | 412 _logEvent("Error creating launch HTML: $error"); |
| 413 return false; |
| 414 } |
| 415 var args = [ |
| 416 "-d", "-i", "-m", "-s", "-u", _binary, |
| 417 "${userDir.path}/launch.html"]; |
| 418 try { |
| 419 return startBrowserProcess("/usr/bin/caffeinate", args); |
| 420 } catch (error) { |
| 421 _logEvent("Error starting browser process: $error"); |
| 422 return false; |
| 423 } |
| 424 } |
| 425 |
| 426 Future<Null> onDriverPageRequested() async { |
| 427 await Process.run("/usr/bin/osascript", |
| 428 ['-e', 'tell application "Safari" to activate']); |
408 } | 429 } |
409 | 430 |
410 String toString() => "Safari"; | 431 String toString() => "Safari"; |
411 } | 432 } |
412 | 433 |
413 class Chrome extends Browser { | 434 class Chrome extends Browser { |
414 String _version = "Version not found yet"; | 435 String _version = "Version not found yet"; |
415 | 436 |
416 Map<String, String> _getEnvironment() => null; | 437 Map<String, String> _getEnvironment() => null; |
417 | 438 |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
470 return false; | 491 return false; |
471 }); | 492 }); |
472 } | 493 } |
473 | 494 |
474 String toString() => "Chrome"; | 495 String toString() => "Chrome"; |
475 } | 496 } |
476 | 497 |
477 class SafariMobileSimulator extends Safari { | 498 class SafariMobileSimulator extends Safari { |
478 /** | 499 /** |
479 * Directories where safari simulator stores state. We delete these if the | 500 * Directories where safari simulator stores state. We delete these if the |
480 * deleteCache is set | 501 * resetBrowserConfiguration is set |
481 */ | 502 */ |
482 static const List<String> CACHE_DIRECTORIES = const [ | 503 static const List<String> CACHE_DIRECTORIES = const [ |
483 "Library/Application Support/iPhone Simulator/7.1/Applications" | 504 "Library/Application Support/iPhone Simulator/7.1/Applications" |
484 ]; | 505 ]; |
485 | 506 |
486 // Clears the cache if the static deleteCache flag is set. | 507 // Clears the cache if the static resetBrowserConfiguration flag is set. |
487 // Returns false if the command to actually clear the cache did not complete. | 508 // Returns false if the command to actually clear the cache did not complete. |
488 Future<bool> clearCache() { | 509 Future<bool> resetConfiguration() { |
489 if (!Browser.deleteCache) return new Future.value(true); | 510 if (!Browser.resetBrowserConfiguration) return new Future.value(true); |
490 var home = Platform.environment['HOME']; | 511 var home = Platform.environment['HOME']; |
491 Iterator iterator = CACHE_DIRECTORIES.map((s) => "$home/$s").iterator; | 512 Iterator iterator = CACHE_DIRECTORIES.map((s) => "$home/$s").iterator; |
492 return deleteIfExists(iterator); | 513 return deleteIfExists(iterator); |
493 } | 514 } |
494 | 515 |
495 Future<bool> start(String url) { | 516 Future<bool> start(String url) { |
496 _logEvent("Starting safari mobile simulator browser on: $url"); | 517 _logEvent("Starting safari mobile simulator browser on: $url"); |
497 return clearCache().then((success) { | 518 return resetConfiguration().then((success) { |
498 if (!success) { | 519 if (!success) { |
499 _logEvent("Could not clear cache, exiting"); | 520 _logEvent("Could not clear cache, exiting"); |
500 return false; | 521 return false; |
501 } | 522 } |
502 var args = [ | 523 var args = [ |
503 "-SimulateApplication", | 524 "-SimulateApplication", |
504 "/Applications/Xcode.app/Contents/Developer/Platforms/" | 525 "/Applications/Xcode.app/Contents/Developer/Platforms/" |
505 "iPhoneSimulator.platform/Developer/SDKs/" | 526 "iPhoneSimulator.platform/Developer/SDKs/" |
506 "iPhoneSimulator7.1.sdk/Applications/MobileSafari.app/" | 527 "iPhoneSimulator7.1.sdk/Applications/MobileSafari.app/" |
507 "MobileSafari", | 528 "MobileSafari", |
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
554 var findString = "REG_SZ"; | 575 var findString = "REG_SZ"; |
555 var index = result.stdout.indexOf(findString); | 576 var index = result.stdout.indexOf(findString); |
556 if (index > 0) { | 577 if (index > 0) { |
557 return result.stdout.substring(index + findString.length).trim(); | 578 return result.stdout.substring(index + findString.length).trim(); |
558 } | 579 } |
559 } | 580 } |
560 return "Could not get the version of internet explorer"; | 581 return "Could not get the version of internet explorer"; |
561 }); | 582 }); |
562 } | 583 } |
563 | 584 |
564 // Clears the recovery cache if the static deleteCache flag is set. | 585 // Clears the recovery cache if the static resetBrowserConfiguration flag is |
565 Future<bool> clearCache() { | 586 // set. |
566 if (!Browser.deleteCache) return new Future.value(true); | 587 Future<bool> resetConfiguration() { |
| 588 if (!Browser.resetBrowserConfiguration) return new Future.value(true); |
567 var localAppData = Platform.environment['LOCALAPPDATA']; | 589 var localAppData = Platform.environment['LOCALAPPDATA']; |
568 | 590 |
569 Directory dir = new Directory("$localAppData\\Microsoft\\" | 591 Directory dir = new Directory("$localAppData\\Microsoft\\" |
570 "Internet Explorer\\Recovery"); | 592 "Internet Explorer\\Recovery"); |
571 return dir.delete(recursive: true).then((_) { | 593 return dir.delete(recursive: true).then((_) { |
572 return true; | 594 return true; |
573 }).catchError((error) { | 595 }).catchError((error) { |
574 _logEvent("Deleting recovery dir failed with $error"); | 596 _logEvent("Deleting recovery dir failed with $error"); |
575 return false; | 597 return false; |
576 }); | 598 }); |
577 } | 599 } |
578 | 600 |
579 Future<bool> start(String url) { | 601 Future<bool> start(String url) { |
580 _logEvent("Starting ie browser on: $url"); | 602 _logEvent("Starting ie browser on: $url"); |
581 return clearCache().then((_) => getVersion()).then((version) { | 603 return resetConfiguration().then((_) => getVersion()).then((version) { |
582 _logEvent("Got version: $version"); | 604 _logEvent("Got version: $version"); |
583 return startBrowserProcess(_binary, [url]); | 605 return startBrowserProcess(_binary, [url]); |
584 }); | 606 }); |
585 } | 607 } |
586 | 608 |
587 String toString() => "IE"; | 609 String toString() => "IE"; |
588 } | 610 } |
589 | 611 |
590 class AndroidBrowserConfig { | 612 class AndroidBrowserConfig { |
591 final String name; | 613 final String name; |
(...skipping 277 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
869 /// requests back from the browsers. | 891 /// requests back from the browsers. |
870 class BrowserTestRunner { | 892 class BrowserTestRunner { |
871 static const int MAX_NEXT_TEST_TIMEOUTS = 10; | 893 static const int MAX_NEXT_TEST_TIMEOUTS = 10; |
872 static const Duration NEXT_TEST_TIMEOUT = const Duration(seconds: 60); | 894 static const Duration NEXT_TEST_TIMEOUT = const Duration(seconds: 60); |
873 static const Duration RESTART_BROWSER_INTERVAL = const Duration(seconds: 60); | 895 static const Duration RESTART_BROWSER_INTERVAL = const Duration(seconds: 60); |
874 | 896 |
875 /// If the queue was recently empty, don't start another browser. | 897 /// If the queue was recently empty, don't start another browser. |
876 static const Duration MIN_NONEMPTY_QUEUE_TIME = const Duration(seconds: 1); | 898 static const Duration MIN_NONEMPTY_QUEUE_TIME = const Duration(seconds: 1); |
877 | 899 |
878 final Map configuration; | 900 final Map configuration; |
879 BrowserTestingServer testingServer; | 901 final BrowserTestingServer testingServer; |
880 | 902 |
881 final String localIp; | 903 final String localIp; |
882 String browserName; | 904 final String browserName; |
883 int maxNumBrowsers; | 905 final int maxNumBrowsers; |
884 bool checkedMode; | 906 final bool checkedMode; |
885 int numBrowsers = 0; | 907 int numBrowsers = 0; |
886 // Used to send back logs from the browser (start, stop etc) | 908 // Used to send back logs from the browser (start, stop etc) |
887 Function logger; | 909 Function logger; |
888 | 910 |
889 int browserIdCounter = 1; | 911 int browserIdCounter = 1; |
890 | 912 |
891 bool testingServerStarted = false; | 913 bool testingServerStarted = false; |
892 bool underTermination = false; | 914 bool underTermination = false; |
893 int numBrowserGetTestTimeouts = 0; | 915 int numBrowserGetTestTimeouts = 0; |
894 DateTime lastEmptyTestQueueTime = new DateTime.now(); | 916 DateTime lastEmptyTestQueueTime = new DateTime.now(); |
(...skipping 26 matching lines...) Expand all Loading... |
921 // When no browser is currently starting, _currentStartingBrowserId is null. | 943 // When no browser is currently starting, _currentStartingBrowserId is null. |
922 bool get aBrowserIsCurrentlyStarting => _currentStartingBrowserId != null; | 944 bool get aBrowserIsCurrentlyStarting => _currentStartingBrowserId != null; |
923 void markCurrentlyStarting(String id) { | 945 void markCurrentlyStarting(String id) { |
924 _currentStartingBrowserId = id; | 946 _currentStartingBrowserId = id; |
925 } | 947 } |
926 | 948 |
927 void markNotCurrentlyStarting(String id) { | 949 void markNotCurrentlyStarting(String id) { |
928 if (_currentStartingBrowserId == id) _currentStartingBrowserId = null; | 950 if (_currentStartingBrowserId == id) _currentStartingBrowserId = null; |
929 } | 951 } |
930 | 952 |
931 // If [browserName] doesn't support opening new windows, we use new iframes | |
932 // instead. | |
933 bool get useIframe => | |
934 !Browser.BROWSERS_WITH_WINDOW_SUPPORT.contains(browserName); | |
935 | |
936 /// The optional testingServer parameter allows callers to pass in | |
937 /// a testing server with different behavior than the default | |
938 /// BrowserTestServer. The url handlers of the testingServer are | |
939 /// overwritten, so an existing handler can't be shared between instances. | |
940 BrowserTestRunner( | 953 BrowserTestRunner( |
941 this.configuration, this.localIp, this.browserName, this.maxNumBrowsers, | 954 Map configuration, |
942 {BrowserTestingServer this.testingServer}) { | 955 String localIp, |
943 checkedMode = configuration['checked']; | 956 String browserName, |
944 if (browserName == 'ff') browserName = 'firefox'; | 957 this.maxNumBrowsers) |
| 958 : configuration = configuration, |
| 959 localIp = localIp, |
| 960 browserName = (browserName == 'ff') ? 'firefox' : browserName, |
| 961 checkedMode = configuration['checked'], |
| 962 testingServer = new BrowserTestingServer( |
| 963 configuration, localIp, |
| 964 Browser.requiresIframe(browserName), |
| 965 Browser.requiresFocus(browserName)) { |
| 966 testingServer.testRunner = this; |
945 } | 967 } |
946 | 968 |
947 Future start() async { | 969 Future start() async { |
948 if (testingServer == null) { | |
949 testingServer = | |
950 new BrowserTestingServer(configuration, localIp, useIframe); | |
951 } | |
952 await testingServer.start(); | 970 await testingServer.start(); |
953 testingServer | 971 testingServer |
954 ..testDoneCallBack = handleResults | 972 ..testDoneCallBack = handleResults |
955 ..testStatusUpdateCallBack = handleStatusUpdate | 973 ..testStatusUpdateCallBack = handleStatusUpdate |
956 ..testStartedCallBack = handleStarted | 974 ..testStartedCallBack = handleStarted |
957 ..nextTestCallBack = getNextTest; | 975 ..nextTestCallBack = getNextTest; |
958 if (browserName == 'chromeOnAndroid') { | 976 if (browserName == 'chromeOnAndroid') { |
959 var idbNames = await AdbHelper.listDevices(); | 977 var idbNames = await AdbHelper.listDevices(); |
960 idleAdbDevices = new List.from(idbNames.map((id) => new AdbDevice(id))); | 978 idleAdbDevices = new List.from(idbNames.map((id) => new AdbDevice(id))); |
961 maxNumBrowsers = min(maxNumBrowsers, idleAdbDevices.length); | 979 maxNumBrowsers = min(maxNumBrowsers, idleAdbDevices.length); |
(...skipping 316 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1278 /// and run tests ... | 1296 /// and run tests ... |
1279 /// GET /next_test/BROWSER_ID -- returns "WAIT" "TERMINATE" or "url#id" | 1297 /// GET /next_test/BROWSER_ID -- returns "WAIT" "TERMINATE" or "url#id" |
1280 /// where url is the test to run, and id is the id of the test. | 1298 /// where url is the test to run, and id is the id of the test. |
1281 /// If there are currently no available tests the waitSignal is send | 1299 /// If there are currently no available tests the waitSignal is send |
1282 /// back. If we are in the process of terminating the terminateSignal | 1300 /// back. If we are in the process of terminating the terminateSignal |
1283 /// is send back and the browser will stop requesting new tasks. | 1301 /// is send back and the browser will stop requesting new tasks. |
1284 /// POST /report/BROWSER_ID?id=NUM -- sends back the dom of the executed | 1302 /// POST /report/BROWSER_ID?id=NUM -- sends back the dom of the executed |
1285 /// test | 1303 /// test |
1286 | 1304 |
1287 final String localIp; | 1305 final String localIp; |
| 1306 final bool useIframe; |
| 1307 final bool requiresFocus; |
| 1308 BrowserTestRunner testRunner; |
1288 | 1309 |
1289 static const String driverPath = "/driver"; | 1310 static const String driverPath = "/driver"; |
1290 static const String nextTestPath = "/next_test"; | 1311 static const String nextTestPath = "/next_test"; |
1291 static const String reportPath = "/report"; | 1312 static const String reportPath = "/report"; |
1292 static const String statusUpdatePath = "/status_update"; | 1313 static const String statusUpdatePath = "/status_update"; |
1293 static const String startedPath = "/started"; | 1314 static const String startedPath = "/started"; |
1294 static const String waitSignal = "WAIT"; | 1315 static const String waitSignal = "WAIT"; |
1295 static const String terminateSignal = "TERMINATE"; | 1316 static const String terminateSignal = "TERMINATE"; |
1296 | 1317 |
1297 var testCount = 0; | 1318 var testCount = 0; |
1298 var errorReportingServer; | 1319 var errorReportingServer; |
1299 bool underTermination = false; | 1320 bool underTermination = false; |
1300 bool useIframe = false; | |
1301 | 1321 |
1302 Function testDoneCallBack; | 1322 Function testDoneCallBack; |
1303 Function testStatusUpdateCallBack; | 1323 Function testStatusUpdateCallBack; |
1304 Function testStartedCallBack; | 1324 Function testStartedCallBack; |
1305 Function nextTestCallBack; | 1325 Function nextTestCallBack; |
1306 | 1326 |
1307 BrowserTestingServer(this.configuration, this.localIp, this.useIframe); | 1327 BrowserTestingServer( |
| 1328 this.configuration, this.localIp, this.useIframe, this.requiresFocus); |
1308 | 1329 |
1309 Future start() { | 1330 Future start() { |
1310 var test_driver_error_port = configuration['test_driver_error_port']; | 1331 var test_driver_error_port = configuration['test_driver_error_port']; |
1311 return HttpServer | 1332 return HttpServer |
1312 .bind(localIp, test_driver_error_port) | 1333 .bind(localIp, test_driver_error_port) |
1313 .then(setupErrorServer) | 1334 .then(setupErrorServer) |
1314 .then(setupDispatchingServer); | 1335 .then(setupDispatchingServer); |
1315 } | 1336 } |
1316 | 1337 |
1317 void setupErrorServer(HttpServer server) { | 1338 void setupErrorServer(HttpServer server) { |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1359 noCache(request); | 1380 noCache(request); |
1360 handleReport( | 1381 handleReport( |
1361 request, browserId(request, statusUpdatePath), testId(request), | 1382 request, browserId(request, statusUpdatePath), testId(request), |
1362 isStatusUpdate: true); | 1383 isStatusUpdate: true); |
1363 }); | 1384 }); |
1364 server.addHandler(startedPath, (HttpRequest request) { | 1385 server.addHandler(startedPath, (HttpRequest request) { |
1365 noCache(request); | 1386 noCache(request); |
1366 handleStarted(request, browserId(request, startedPath), testId(request)); | 1387 handleStarted(request, browserId(request, startedPath), testId(request)); |
1367 }); | 1388 }); |
1368 | 1389 |
1369 makeSendPageHandler(String prefix) => (HttpRequest request) { | 1390 void sendPageHandler(HttpRequest request) { |
1370 noCache(request); | 1391 // Do NOT make this method async. We need to call catchError below |
1371 var textResponse = ""; | 1392 // synchronously to avoid unhandled asynchronous errors. |
1372 if (prefix == driverPath) { | 1393 noCache(request); |
1373 textResponse = getDriverPage(browserId(request, prefix)); | 1394 Future<String> textResponse; |
1374 request.response.headers.set('Content-Type', 'text/html'); | 1395 if (request.uri.path.startsWith(driverPath)) { |
1375 } | 1396 textResponse = getDriverPage(browserId(request, driverPath)); |
1376 if (prefix == nextTestPath) { | 1397 request.response.headers.set('Content-Type', 'text/html'); |
1377 textResponse = getNextTest(browserId(request, prefix)); | 1398 } else if (request.uri.path.startsWith(nextTestPath)) { |
1378 request.response.headers.set('Content-Type', 'text/plain'); | 1399 textResponse = new Future<String>.value( |
1379 } | 1400 getNextTest(browserId(request, nextTestPath))); |
1380 request.response.write(textResponse); | 1401 request.response.headers.set('Content-Type', 'text/plain'); |
1381 request.listen((_) {}, onDone: request.response.close); | 1402 } else { |
1382 request.response.done.catchError((error) { | 1403 textResponse = new Future<String>.value(""); |
1383 if (!underTermination) { | 1404 } |
1384 print("URI ${request.uri}"); | 1405 request.response.done.catchError((error) { |
1385 print("Textresponse $textResponse"); | 1406 if (!underTermination) { |
1386 throw "Error returning content to browser: $error"; | 1407 return textResponse.then((String text) { |
1387 } | 1408 print("URI ${request.uri}"); |
| 1409 print("textResponse $textResponse"); |
| 1410 throw "Error returning content to browser: $error"; |
1388 }); | 1411 }); |
1389 }; | 1412 } |
1390 server.addHandler(driverPath, makeSendPageHandler(driverPath)); | 1413 }); |
1391 server.addHandler(nextTestPath, makeSendPageHandler(nextTestPath)); | 1414 textResponse.then((String text) async { |
| 1415 request.response.write(text); |
| 1416 await request.listen(null).asFuture(); |
| 1417 // Ignoring the returned closure as it returns the 'done' future |
| 1418 // which alread has catchError installed above. |
| 1419 request.response.close(); |
| 1420 }); |
| 1421 } |
| 1422 |
| 1423 server.addHandler(driverPath, sendPageHandler); |
| 1424 server.addHandler(nextTestPath, sendPageHandler); |
1392 } | 1425 } |
1393 | 1426 |
1394 void handleReport(HttpRequest request, String browserId, var testId, | 1427 void handleReport(HttpRequest request, String browserId, var testId, |
1395 {bool isStatusUpdate}) { | 1428 {bool isStatusUpdate}) { |
1396 StringBuffer buffer = new StringBuffer(); | 1429 StringBuffer buffer = new StringBuffer(); |
1397 request.transform(UTF8.decoder).listen((data) { | 1430 request.transform(UTF8.decoder).listen((data) { |
1398 buffer.write(data); | 1431 buffer.write(data); |
1399 }, onDone: () { | 1432 }, onDone: () { |
1400 String back = buffer.toString(); | 1433 String back = buffer.toString(); |
1401 request.response.close(); | 1434 request.response.close(); |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1440 if (errorReportingServer == null) { | 1473 if (errorReportingServer == null) { |
1441 print("Bad browser testing server, you are not started yet. Can't " | 1474 print("Bad browser testing server, you are not started yet. Can't " |
1442 "produce driver url"); | 1475 "produce driver url"); |
1443 exit(1); | 1476 exit(1); |
1444 // This should never happen - exit immediately; | 1477 // This should never happen - exit immediately; |
1445 } | 1478 } |
1446 var port = configuration['_servers_'].port; | 1479 var port = configuration['_servers_'].port; |
1447 return "http://$localIp:$port/driver/$browserId"; | 1480 return "http://$localIp:$port/driver/$browserId"; |
1448 } | 1481 } |
1449 | 1482 |
1450 String getDriverPage(String browserId) { | 1483 Future<String> getDriverPage(String browserId) async { |
| 1484 await testRunner.browserStatus[browserId].browser.onDriverPageRequested(); |
1451 var errorReportingUrl = | 1485 var errorReportingUrl = |
1452 "http://$localIp:${errorReportingServer.port}/$browserId"; | 1486 "http://$localIp:${errorReportingServer.port}/$browserId"; |
1453 String driverContent = """ | 1487 String driverContent = """ |
1454 <!DOCTYPE html><html> | 1488 <!DOCTYPE html><html> |
1455 <head> | 1489 <head> |
| 1490 <title>Driving page</title> |
1456 <style> | 1491 <style> |
1457 body { | 1492 .big-notice { |
1458 margin: 0; | 1493 background-color: red; |
1459 } | 1494 color: white; |
1460 .box { | 1495 font-weight: bold; |
1461 overflow: hidden; | 1496 font-size: xx-large; |
1462 overflow-y: auto; | 1497 text-align: center; |
1463 position: absolute; | 1498 } |
1464 left: 0; | 1499 .controller.box { |
1465 right: 0; | 1500 white-space: nowrap; |
1466 } | 1501 overflow: scroll; |
1467 .controller.box { | 1502 height: 6em; |
1468 height: 75px; | 1503 } |
1469 top: 0; | 1504 body { |
1470 } | 1505 font-family: sans-serif; |
1471 .test.box { | 1506 } |
1472 top: 75px; | 1507 body div { |
1473 bottom: 0; | 1508 padding-top: 10px; |
1474 } | 1509 } |
1475 </style> | 1510 </style> |
1476 <title>Driving page</title> | |
1477 <script type='text/javascript'> | 1511 <script type='text/javascript'> |
1478 var STATUS_UPDATE_INTERVAL = 10000; | 1512 var STATUS_UPDATE_INTERVAL = 10000; |
1479 | 1513 |
1480 function startTesting() { | 1514 function startTesting() { |
1481 var number_of_tests = 0; | 1515 var number_of_tests = 0; |
1482 var current_id; | 1516 var current_id; |
1483 var next_id; | 1517 var next_id; |
1484 | 1518 |
1485 // Has the test in the current iframe reported that it is done? | 1519 // Has the test in the current iframe reported that it is done? |
1486 var test_completed = true; | 1520 var test_completed = true; |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1596 if (use_iframe) { | 1630 if (use_iframe) { |
1597 if (html_test) { | 1631 if (html_test) { |
1598 window.addEventListener('detect_errors', setChildHandlers, false); | 1632 window.addEventListener('detect_errors', setChildHandlers, false); |
1599 embedded_iframe.onload = checkChildHandlersInstalled; | 1633 embedded_iframe.onload = checkChildHandlersInstalled; |
1600 } else { | 1634 } else { |
1601 embedded_iframe.onload = null; | 1635 embedded_iframe.onload = null; |
1602 } | 1636 } |
1603 embedded_iframe_div.removeChild(embedded_iframe); | 1637 embedded_iframe_div.removeChild(embedded_iframe); |
1604 embedded_iframe = document.createElement('iframe'); | 1638 embedded_iframe = document.createElement('iframe'); |
1605 embedded_iframe.id = "embedded_iframe"; | 1639 embedded_iframe.id = "embedded_iframe"; |
1606 embedded_iframe.style="width:100%;height:100%"; | 1640 embedded_iframe.width='800px'; |
| 1641 embedded_iframe.height='600px'; |
1607 embedded_iframe_div.appendChild(embedded_iframe); | 1642 embedded_iframe_div.appendChild(embedded_iframe); |
1608 embedded_iframe.src = url; | 1643 embedded_iframe.src = url; |
1609 } else { | 1644 } else { |
1610 if (typeof testing_window != 'undefined') { | 1645 if (typeof testing_window != 'undefined') { |
1611 testing_window.close(); | 1646 testing_window.close(); |
1612 } | 1647 } |
1613 testing_window = window.open(url); | 1648 testing_window = window.open(url); |
1614 } | 1649 } |
1615 test_started = false; | 1650 test_started = false; |
1616 test_completed = false; | 1651 test_completed = false; |
(...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1768 | 1803 |
1769 if (!html_test) { | 1804 if (!html_test) { |
1770 window.addEventListener('message', messageHandler, false); | 1805 window.addEventListener('message', messageHandler, false); |
1771 waitForDone = false; | 1806 waitForDone = false; |
1772 } | 1807 } |
1773 getNextTask(); | 1808 getNextTask(); |
1774 } | 1809 } |
1775 </script> | 1810 </script> |
1776 </head> | 1811 </head> |
1777 <body onload="startTesting()"> | 1812 <body onload="startTesting()"> |
| 1813 |
| 1814 <div class='big-notice'> |
| 1815 Please keep this window in focus at all times. |
| 1816 </div> |
| 1817 |
| 1818 <div> |
| 1819 Some browsers, Safari, in particular, may pause JavaScript when not |
| 1820 visible to conserve power consumption and CPU resources. In addition, |
| 1821 some tests of focus events will not work correctly if this window doesn't |
| 1822 have focus. It's also advisable to close any other programs that may open |
| 1823 modal dialogs, for example, Chrome with Calendar open. |
| 1824 </div> |
| 1825 |
1778 <div class="controller box"> | 1826 <div class="controller box"> |
1779 Dart test driver, number of tests: <span id="number"></span><br> | 1827 Dart test driver, number of tests: <span id="number"></span><br> |
1780 Currently executing: <span id="currently_executing"></span><br> | 1828 Currently executing: <span id="currently_executing"></span><br> |
1781 Unhandled error: <span id="unhandled_error"></span> | 1829 Unhandled error: <span id="unhandled_error"></span> |
1782 </div> | 1830 </div> |
1783 <div id="embedded_iframe_div" class="test box"> | 1831 <div id="embedded_iframe_div" class="test box"> |
1784 <iframe style="width:100%;height:100%;" id="embedded_iframe"></iframe> | 1832 <iframe id="embedded_iframe"></iframe> |
1785 </div> | 1833 </div> |
1786 </body> | 1834 </body> |
1787 </html> | 1835 </html> |
1788 """; | 1836 """; |
1789 return driverContent; | 1837 return driverContent; |
1790 } | 1838 } |
1791 } | 1839 } |
OLD | NEW |