| 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 |