diff --git a/CHANGELOG.md b/CHANGELOG.md
index f0faab10980ca1498d1754ad1a5e90090f8fe266..b37d10a696b19ffe217965fec1b8406028f81caf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+### 0.12.0-beta.7
+
+* Browser tests can now load assets by making HTTP requests to the corresponding
+  relative URLs.
+
 ### 0.12.0-beta.6
 
 * Add the ability to run multiple test suites concurrently. By default a number
diff --git a/lib/pub_serve.dart b/lib/pub_serve.dart
index f7cf373224829a29cb3e78ad8c2785eae9674032..71fa1cffd516dcbf100a655fd519381b757a9366 100644
--- a/lib/pub_serve.dart
+++ b/lib/pub_serve.dart
@@ -19,16 +19,16 @@ class PubServeTransformer extends Transformer implements DeclaringTransformer {
 
   void declareOutputs(DeclaringTransform transform) {
     var id = transform.primaryId;
-    transform.declareOutput(id.changeExtension('.vm_test.dart'));
-    transform.declareOutput(id.changeExtension('.browser_test.dart'));
-    transform.declareOutput(id.changeExtension('.browser_test.html'));
+    transform.declareOutput(id.addExtension('.vm_test.dart'));
+    transform.declareOutput(id.addExtension('.browser_test.dart'));
+    transform.declareOutput(id.addExtension('.browser_test.html'));
   }
 
   void apply(Transform transform) {
     var id = transform.primaryInput.id;
 
     transform.addOutput(
-        new Asset.fromString(id.changeExtension('.vm_test.dart'), '''
+        new Asset.fromString(id.addExtension('.vm_test.dart'), '''
 import "package:test/src/runner/vm/isolate_listener.dart";
 
 import "${p.url.basename(id.path)}" as test;
@@ -39,7 +39,7 @@ void main(_, Map message) {
 }
 '''));
 
-    var browserId = id.changeExtension('.browser_test.dart');
+    var browserId = id.addExtension('.browser_test.dart');
     transform.addOutput(new Asset.fromString(browserId, '''
 import "package:test/src/runner/browser/iframe_listener.dart";
 
@@ -51,7 +51,7 @@ void main(_) {
 '''));
 
     transform.addOutput(
-        new Asset.fromString(browserId.changeExtension('.html'), '''
+        new Asset.fromString(id.addExtension('.browser_test.html'), '''
 <!DOCTYPE html>
 <html>
 <head>
diff --git a/lib/src/runner/browser/server.dart b/lib/src/runner/browser/server.dart
index a98bcb08dc55c96864391ad7faba476f6b07299e..1c2ee38ead6e8f418b1373894b3b58dde7b9e145 100644
--- a/lib/src/runner/browser/server.dart
+++ b/lib/src/runner/browser/server.dart
@@ -18,6 +18,7 @@ import 'package:shelf_web_socket/shelf_web_socket.dart';
 import '../../backend/suite.dart';
 import '../../backend/test_platform.dart';
 import '../../util/io.dart';
+import '../../util/path_handler.dart';
 import '../../util/one_off_handler.dart';
 import '../../utils.dart';
 import '../load_exception.dart';
@@ -33,6 +34,9 @@ import 'firefox.dart';
 class BrowserServer {
   /// Starts the server.
   ///
+  /// [root] is the root directory that the server should serve. It defaults to
+  /// the working directory.
+  ///
   /// If [packageRoot] is passed, it's used for all package imports when
   /// compiling tests to JS. Otherwise, the package root is inferred from the
   /// location of the source file.
@@ -41,25 +45,35 @@ class BrowserServer {
   /// instance at that URL rather than from the filesystem.
   ///
   /// If [color] is true, console colors will be used when compiling Dart.
-  static Future<BrowserServer> start({String packageRoot, Uri pubServeUrl,
-      bool color: false}) {
-    var server = new BrowserServer._(packageRoot, pubServeUrl, color);
+  static Future<BrowserServer> start({String root, String packageRoot,
+      Uri pubServeUrl, bool color: false}) {
+    var server = new BrowserServer._(root, packageRoot, pubServeUrl, color);
     return server._load().then((_) => server);
   }
 
   /// The underlying HTTP server.
   HttpServer _server;
 
+  /// A randomly-generated secret.
+  ///
+  /// This is used to ensure that other users on the same system can't snoop
+  /// on data being served through this server.
+  final _secret = randomBase64(24, urlSafe: true);
+
   /// The URL for this server.
-  Uri get url => baseUrlForAddress(_server.address, _server.port);
+  Uri get url => baseUrlForAddress(_server.address, _server.port)
+      .resolve(_secret + "/");
 
-  /// a [OneOffHandler] for servicing WebSocket connections for
+  /// A [OneOffHandler] for servicing WebSocket connections for
   /// [BrowserManager]s.
   ///
   /// This is one-off because each [BrowserManager] can only connect to a single
   /// WebSocket,
   final _webSocketHandler = new OneOffHandler();
 
+  /// A [PathHandler] used to serve compiled JS.
+  final _jsHandler = new PathHandler();
+
   /// The [CompilerPool] managing active instances of `dart2js`.
   ///
   /// This is `null` if tests are loaded from `pub serve`.
@@ -68,6 +82,9 @@ class BrowserServer {
   /// The temporary directory in which compiled JS is emitted.
   final String _compiledDir;
 
+  /// The root directory served statically by this server.
+  final String _root;
+
   /// The package root which is passed to `dart2js`.
   final String _packageRoot;
 
@@ -109,8 +126,9 @@ class BrowserServer {
   /// per run, rather than one per browser per run.
   final _compileFutures = new Map<String, Future>();
 
-  BrowserServer._(this._packageRoot, Uri pubServeUrl, bool color)
-      : _pubServeUrl = pubServeUrl,
+  BrowserServer._(String root, this._packageRoot, Uri pubServeUrl, bool color)
+      : _root = root == null ? p.current : root,
+        _pubServeUrl = pubServeUrl,
         _compiledDir = pubServeUrl == null ? createTempDir() : null,
         _http = pubServeUrl == null ? null : new HttpClient(),
         _compilers = new CompilerPool(color: color);
@@ -121,19 +139,70 @@ class BrowserServer {
         .add(_webSocketHandler.handler);
 
     if (_pubServeUrl == null) {
-      var staticPath = p.join(libDir(packageRoot: _packageRoot),
-          'src/runner/browser/static');
       cascade = cascade
-          .add(createStaticHandler(staticPath, defaultDocument: 'index.html'))
-          .add(createStaticHandler(_compiledDir,
-              defaultDocument: 'index.html'));
+          .add(_createPackagesHandler())
+          .add(_jsHandler.handler)
+          .add(_wrapperHandler)
+          .add(createStaticHandler(_root));
     }
 
-    return shelf_io.serve(cascade.handler, 'localhost', 0).then((server) {
+    var pipeline = new shelf.Pipeline()
+      .addMiddleware(nestingMiddleware(_secret))
+      .addHandler(cascade.handler);
+
+    return shelf_io.serve(pipeline, 'localhost', 0).then((server) {
       _server = server;
     });
   }
 
+  /// Returns a handler that serves the contents of the "packages/" directory
+  /// for any URL that contains "packages/".
+  ///
+  /// This is a factory so it can wrap a static handler.
+  shelf.Handler _createPackagesHandler() {
+    var packageRoot = _packageRoot == null
+        ? p.join(_root, 'packages')
+        : _packageRoot;
+    var staticHandler =
+      createStaticHandler(packageRoot, serveFilesOutsidePath: true);
+
+    return (request) {
+      var segments = p.url.split(shelfUrl(request).path);
+
+      for (var i = 0; i < segments.length; i++) {
+        if (segments[i] != "packages") continue;
+        return staticHandler(
+            shelfChange(request, path: p.url.joinAll(segments.take(i + 1))));
+      }
+
+      return new shelf.Response.notFound("Not found.");
+    };
+  }
+
+  /// A handler that serves wrapper HTML to bootstrap tests.
+  shelf.Response _wrapperHandler(shelf.Request request) {
+    var path = p.fromUri(shelfUrl(request));
+    var withoutExtensions = p.withoutExtension(p.withoutExtension(path));
+    var base = p.basename(withoutExtensions);
+
+    if (path.endsWith(".browser_test.html")) {
+      // TODO(nweiz): support user-authored HTML files.
+      return new shelf.Response.ok('''
+<!DOCTYPE html>
+<html>
+<head>
+  <title>${HTML_ESCAPE.convert(base)}.dart Test</title>
+  <script type="application/javascript"
+          src="${HTML_ESCAPE.convert(base)}.browser_test.dart.js">
+  </script>
+</head>
+</html>
+''', headers: {'Content-Type': 'text/html'});
+    }
+
+    return new shelf.Response.notFound('Not found.');
+  }
+
   /// Loads the test suite at [path] on the browser [browser].
   ///
   /// This will start a browser to load the suite if one isn't already running.
@@ -145,22 +214,18 @@ class BrowserServer {
 
     return new Future.sync(() {
       if (_pubServeUrl != null) {
-        var suitePrefix = p.withoutExtension(p.relative(path, from: 'test')) +
+        var suitePrefix = p.relative(path, from: p.join(_root, 'test')) +
             '.browser_test';
         var jsUrl = _pubServeUrl.resolve('$suitePrefix.dart.js');
-        return _pubServeSuite(path, jsUrl)
-            .then((_) => _pubServeUrl.resolve('$suitePrefix.html'));
-      } else {
-        return _compileSuite(path).then((dir) {
-          if (_closed) return null;
-
-          // Add a trailing slash because at least on Chrome, the iframe's
-          // window.location.href will do so automatically, and if that differs
-          // from the original URL communication will fail.
-          return url.resolve(
-              "/" + p.toUri(p.relative(dir, from: _compiledDir)).path + "/");
-        });
+        return _pubServeSuite(path, jsUrl).then((_) =>
+            _pubServeUrl.resolve('$suitePrefix.html'));
       }
+
+      return _compileSuite(path).then((_) {
+        if (_closed) return null;
+        return url.resolveUri(
+            p.toUri(p.relative(path, from: _root) + ".browser_test.html"));
+      });
     }).then((suiteUrl) {
       if (_closed) return null;
 
@@ -210,27 +275,24 @@ class BrowserServer {
 
   /// Compile the test suite at [dartPath] to JavaScript.
   ///
-  /// Returns a [Future] that completes to the path to the JavaScript.
-  Future<String> _compileSuite(String dartPath) {
+  /// Once the suite has been compiled, it's added to [_jsHandler] so it can be
+  /// served.
+  Future _compileSuite(String dartPath) {
     return _compileFutures.putIfAbsent(dartPath, () {
       var dir = new Directory(_compiledDir).createTempSync('test_').path;
       var jsPath = p.join(dir, p.basename(dartPath) + ".js");
+
       return _compilers.compile(dartPath, jsPath,
               packageRoot: packageRootFor(dartPath, _packageRoot))
           .then((_) {
-        if (_closed) return null;
+        if (_closed) return;
 
-        // TODO(nweiz): support user-authored HTML files.
-        new File(p.join(dir, "index.html")).writeAsStringSync('''
-<!DOCTYPE html>
-<html>
-<head>
-  <title>${HTML_ESCAPE.convert(dartPath)} Test</title>
-  <script src="${HTML_ESCAPE.convert(p.basename(jsPath))}"></script>
-</head>
-</html>
-''');
-        return dir;
+        _jsHandler.add(
+            p.relative(dartPath, from: _root) + '.browser_test.dart.js',
+            (request) {
+          return new shelf.Response.ok(new File(jsPath).readAsStringSync(),
+              headers: {'Content-Type': 'application/javascript'});
+        });
       });
     });
   }
@@ -248,13 +310,10 @@ class BrowserServer {
       completer.complete(new BrowserManager(webSocket));
     }));
 
-    var webSocketUrl = url.replace(scheme: 'ws', path: '/$path');
+    var webSocketUrl = url.replace(scheme: 'ws').resolve(path);
 
-    var hostUrl = url;
-    if (_pubServeUrl != null) {
-      hostUrl = _pubServeUrl.resolve(
-          '/packages/test/src/runner/browser/static/');
-    }
+    var hostUrl = (_pubServeUrl == null ? url : _pubServeUrl)
+        .resolve('packages/test/src/runner/browser/static/index.html');
 
     var browser = _newBrowser(hostUrl.replace(queryParameters: {
       'managerUrl': webSocketUrl.toString()
diff --git a/lib/src/runner/loader.dart b/lib/src/runner/loader.dart
index afff40a5544d3fd99a3228a7d783c15ee01628e4..475e10d5f277024a6c4a9c281b9928362d033ae5 100644
--- a/lib/src/runner/loader.dart
+++ b/lib/src/runner/loader.dart
@@ -32,6 +32,9 @@ class Loader {
   /// Whether to enable colors for Dart compilation.
   final bool _color;
 
+  /// The root directory that will be served for browser tests.
+  final String _root;
+
   /// The package root to use for loading tests, or `null` to use the automatic
   /// root.
   final String _packageRoot;
@@ -51,6 +54,7 @@ class Loader {
     if (_browserServerCompleter == null) {
       _browserServerCompleter = new Completer();
       BrowserServer.start(
+              root: _root,
               packageRoot: _packageRoot,
               pubServeUrl: _pubServeUrl,
               color: _color)
@@ -63,6 +67,9 @@ class Loader {
 
   /// Creates a new loader.
   ///
+  /// [root] is the root directory that will be served for browser tests. It
+  /// defaults to the working directory.
+  ///
   /// If [packageRoot] is passed, it's used as the package root for all loaded
   /// tests. Otherwise, the `packages/` directories next to the test entrypoints
   /// will be used.
@@ -71,10 +78,11 @@ class Loader {
   /// instance at that URL rather than from the filesystem.
   ///
   /// If [color] is true, console colors will be used when compiling Dart.
-  Loader(Iterable<TestPlatform> platforms, {String packageRoot,
+  Loader(Iterable<TestPlatform> platforms, {String root, String packageRoot,
         Uri pubServeUrl, bool color: false})
       : _platforms = platforms.toList(),
         _pubServeUrl = pubServeUrl,
+        _root = root == null ? p.current : root,
         _packageRoot = packageRoot,
         _color = color;
 
@@ -154,8 +162,7 @@ class Loader {
     return new Future.sync(() {
       if (_pubServeUrl != null) {
         var url = _pubServeUrl.resolve(
-            p.withoutExtension(p.relative(path, from: 'test')) +
-                '.vm_test.dart');
+            p.relative(path, from: 'test') + '.vm_test.dart');
         return Isolate.spawnUri(url, [], {'reply': receivePort.sendPort})
             .then((isolate) => new IsolateWrapper(isolate, () {}))
             .catchError((error, stackTrace) {
diff --git a/lib/src/util/one_off_handler.dart b/lib/src/util/one_off_handler.dart
index 069d6818875d0d4028f022be2d5060cd2537d61a..442a76486590504b3c606904899641d2be525290 100644
--- a/lib/src/util/one_off_handler.dart
+++ b/lib/src/util/one_off_handler.dart
@@ -2,11 +2,13 @@
 // 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.one_off_handler;
+library test.util.one_off_handler;
 
 import 'package:path/path.dart' as p;
 import 'package:shelf/shelf.dart' as shelf;
 
+import '../utils.dart';
+
 /// A Shelf handler that provides support for one-time handlers.
 ///
 /// This is useful for handlers that only expect to be hit once before becoming
@@ -36,18 +38,12 @@ class OneOffHandler {
 
   /// Dispatches [request] to the appropriate handler.
   _onRequest(shelf.Request request) {
-    var components = p.url.split(request.url.path);
-
-    // For shelf < 0.6.0, the first component of the path is always "/". We can
-    // safely skip it.
-    if (components.isNotEmpty && components.first == "/") {
-      components.removeAt(0);
-    }
-
+    var components = p.url.split(shelfUrl(request).path);
     if (components.isEmpty) return new shelf.Response.notFound(null);
 
-    var handler = _handlers.remove(components.removeAt(0));
+    var path = components.removeAt(0);
+    var handler = _handlers.remove(path);
     if (handler == null) return new shelf.Response.notFound(null);
-    return handler(request);
+    return handler(shelfChange(request, path: path));
   }
 }
diff --git a/lib/src/util/path_handler.dart b/lib/src/util/path_handler.dart
new file mode 100644
index 0000000000000000000000000000000000000000..45ce45a22b7567876cd2cd37f2db9671cea169b1
--- /dev/null
+++ b/lib/src/util/path_handler.dart
@@ -0,0 +1,58 @@
+// 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.util.path_handler;
+
+import 'package:path/path.dart' as p;
+import 'package:shelf/shelf.dart' as shelf;
+
+import '../utils.dart';
+
+/// A handler that routes to sub-handlers based on exact path prefixes.
+class PathHandler {
+  /// A trie of path components to handlers.
+  final _paths = new _Node();
+
+  /// The shelf handler.
+  shelf.Handler get handler => _onRequest;
+
+  PathHandler();
+
+  /// Routes requests at or under [path] to [handler].
+  ///
+  /// If [path] is a parent or child directory of another path in this handler,
+  /// the longest matching prefix wins.
+  void add(String path, shelf.Handler handler) {
+    var node = _paths;
+    for (var component in p.url.split(path)) {
+      node = node.children.putIfAbsent(component, () => new _Node());
+    }
+    node.handler = handler;
+  }
+
+  _onRequest(shelf.Request request) {
+    var handler;
+    var handlerIndex;
+    var node = _paths;
+    var components = p.url.split(shelfUrl(request).path);
+    for (var i = 0; i < components.length; i++ ) {
+      node = node.children[components[i]];
+      if (node == null) break;
+      if (node.handler == null) continue;
+      handler = node.handler;
+      handlerIndex = i;
+    }
+
+    if (handler == null) return new shelf.Response.notFound("Not found.");
+
+    return handler(shelfChange(request,
+        path: p.joinAll(components.take(handlerIndex + 1))));
+  }
+}
+
+/// A trie node.
+class _Node {
+  shelf.Handler handler;
+  final children = new Map<String, _Node>();
+}
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 7d1094edd970080d7e04b8690353efb78633cad2..37f2e6639248a30d20dd15e6d7293f54bd11b286 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -5,11 +5,15 @@
 library test.utils;
 
 import 'dart:async';
+import 'dart:math' as math;
 
+import 'package:crypto/crypto.dart';
 import 'package:path/path.dart' as p;
+import 'package:shelf/shelf.dart' as shelf;
 import 'package:stack_trace/stack_trace.dart';
 
 import 'backend/operating_system.dart';
+import 'util/path_handler.dart';
 
 /// A typedef for a possibly-asynchronous function.
 ///
@@ -179,3 +183,62 @@ Stream mergeStreams(Iterable<Stream> streamIter) {
 
   return controller.stream;
 }
+
+/// Returns a random base64 string containing [bytes] bytes of data.
+///
+/// [seed] is passed to [math.Random]; [urlSafe] and [addLineSeparator] are
+/// passed to [CryptoUtils.bytesToBase64].
+String randomBase64(int bytes, {int seed, bool urlSafe: false,
+    bool addLineSeparator: false}) {
+  var random = new math.Random(seed);
+  var data = [];
+  for (var i = 0; i < bytes; i++) {
+    data.add(random.nextInt(256));
+  }
+  return CryptoUtils.bytesToBase64(data,
+      urlSafe: urlSafe, addLineSeparator: addLineSeparator);
+}
+
+// TODO(nweiz): Remove this and [shelfChange] once Shelf 0.6.0 has been out for
+// six months or so.
+/// Returns `request.url` in a cross-version way.
+///
+/// This follows the semantics of Shelf 0.6.x, even when using Shelf 0.5.x: the
+/// returned URL never starts with "/".
+Uri shelfUrl(shelf.Request request) {
+  var url = request.url;
+  if (!url.path.startsWith("/")) return url;
+  return url.replace(path: url.path.replaceFirst("/", ""));
+}
+
+/// Like [shelf.Request.change], but cross-version.
+///
+/// This follows the semantics of Shelf 0.6.x, even when using Shelf 0.5.x.
+shelf.Request shelfChange(shelf.Request typedRequest, {String path}) {
+  // Explicitly make the request dynamic since we're calling methods here that
+  // aren't defined in all support Shelf versions, and we don't want the
+  // analyzer to complain.
+  var request = typedRequest as dynamic;
+
+  try {
+    return request.change(path: path);
+  } on NoSuchMethodError catch (_) {
+    var newScriptName = p.url.join(request.scriptName, path);
+    if (request.scriptName.isEmpty) newScriptName = "/" + newScriptName;
+
+    var newUrlPath = p.url.relative(request.url.path.replaceFirst("/", ""),
+        from: path);
+    newUrlPath = newUrlPath == "." ? "" : "/" + newUrlPath;
+
+    return request.change(
+        scriptName: newScriptName, url: request.url.replace(path: newUrlPath));
+  }
+}
+
+/// Returns middleware that nests all requests beneath the URL prefix [beneath].
+shelf.Middleware nestingMiddleware(String beneath) {
+  return (handler) {
+    var pathHandler = new PathHandler()..add(beneath, handler);
+    return pathHandler.handler;
+  };
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index f28b7cb5cb1b0b9798737de7c2cc3753d7f51e7d..3aedd5852f94b379bcdefe6bab9370b87189caae 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: test
-version: 0.12.0-beta.6
+version: 0.12.0-dev
 author: Dart Team <misc@dartlang.org>
 description: A library for writing dart unit tests.
 homepage: https://github.com/dart-lang/test
@@ -9,6 +9,7 @@ dependencies:
   analyzer: '>=0.23.0 <0.25.0'
   args: '>=0.12.1 <0.14.0'
   barback: '>=0.14.0 <0.16.0'
+  crypto: '^0.9.0'
   http_parser: '^0.0.2'
   pool: '^1.0.0'
   pub_semver: '^1.0.0'
diff --git a/test/runner/browser/loader_test.dart b/test/runner/browser/loader_test.dart
index a3e93c5fe2c20cf6c957a3bdfd187a174a38bf39..322de04cd04951fa23aae36b8a98860f71eda259 100644
--- a/test/runner/browser/loader_test.dart
+++ b/test/runner/browser/loader_test.dart
@@ -33,9 +33,10 @@ void main() {
 
 void main() {
   setUp(() {
+    _sandbox = createTempDir();
     _loader = new Loader([TestPlatform.chrome],
+        root: _sandbox,
         packageRoot: p.join(packageDir, 'packages'));
-    _sandbox = createTempDir();
     /// TODO(nweiz): Use scheduled_test for this once it's compatible with this
     /// version of test.
     new File(p.join(_sandbox, 'a_test.dart')).writeAsStringSync(_tests);
@@ -88,7 +89,7 @@ void main() {
   });
 
   test("throws a nice error if the package root doesn't exist", () {
-    var loader = new Loader([TestPlatform.chrome]);
+    var loader = new Loader([TestPlatform.chrome], root: _sandbox);
     expect(
         loader.loadFile(p.join(_sandbox, 'a_test.dart')).first
             .whenComplete(loader.close),
@@ -98,6 +99,7 @@ void main() {
 
   test("loads a suite both in the browser and the VM", () {
     var loader = new Loader([TestPlatform.vm, TestPlatform.chrome],
+        root: _sandbox,
         packageRoot: p.join(packageDir, 'packages'));
     var path = p.join(_sandbox, 'a_test.dart');
     return loader.loadFile(path).toList().then((suites) {
diff --git a/test/runner/loader_test.dart b/test/runner/loader_test.dart
index f5e8160848097826367189249ce2d49dfb228345..8474cbedbc4ebfa713af46b3077bce88babbd349 100644
--- a/test/runner/loader_test.dart
+++ b/test/runner/loader_test.dart
@@ -33,9 +33,10 @@ void main() {
 
 void main() {
   setUp(() {
+    _sandbox = createTempDir();
     _loader = new Loader([TestPlatform.vm],
+        root: _sandbox,
         packageRoot: p.join(packageDir, 'packages'));
-    _sandbox = createTempDir();
   });
 
   tearDown(() {
@@ -87,7 +88,7 @@ void main() {
     });
 
     test("throws a nice error if the package root doesn't exist", () {
-      var loader = new Loader([TestPlatform.vm]);
+      var loader = new Loader([TestPlatform.vm], root: _sandbox);
       expect(
           loader.loadFile(p.join(_sandbox, 'a_test.dart')).first
               .whenComplete(loader.close),
diff --git a/test/runner/pub_serve_test.dart b/test/runner/pub_serve_test.dart
index 6304b8cfe68add1d6863dfaf0aedef9ba6fd5ec2..d15370f5dd9ee2ad2e3cb3330f4676d7898082af 100644
--- a/test/runner/pub_serve_test.dart
+++ b/test/runner/pub_serve_test.dart
@@ -223,7 +223,7 @@ transformers:
       contains('-1: load error'),
       contains('''
   Failed to load "test/my_test.dart":
-  Error getting http://localhost:54321/my_test.vm_test.dart: Connection refused
+  Error getting http://localhost:54321/my_test.dart.vm_test.dart: Connection refused
   Make sure "pub serve" is running.''')
     ]));
     expect(result.exitCode, equals(1));
@@ -235,8 +235,8 @@ transformers:
     expect(result.stdout, allOf([
       contains('-1: load error'),
       contains('Failed to load "test/my_test.dart":'),
-      contains('Error getting http://localhost:54321/my_test.browser_test.dart'
-          '.js: Connection refused (errno '),
+      contains('Error getting http://localhost:54321/my_test.dart.browser_test'
+          '.dart.js: Connection refused (errno '),
       contains('Make sure "pub serve" is running.')
     ]));
     expect(result.exitCode, equals(1));
diff --git a/test/util/path_handler_test.dart b/test/util/path_handler_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..d6ea38f12f84b92140906c57b2de9bf36063e259
--- /dev/null
+++ b/test/util/path_handler_test.dart
@@ -0,0 +1,77 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:test/src/util/path_handler.dart';
+import 'package:test/test.dart';
+
+void main() {
+  var handler;
+  setUp(() => handler = new PathHandler());
+
+  _handle(request) => new Future.sync(() => handler.handler(request));
+
+  test("returns a 404 for a root URL", () {
+    var request = new shelf.Request("GET", Uri.parse("http://localhost/"));
+    return _handle(request).then((response) {
+      expect(response.statusCode, equals(404));
+    });
+  });
+
+  test("returns a 404 for an unregistered URL", () {
+    var request = new shelf.Request("GET", Uri.parse("http://localhost/foo"));
+    return _handle(request).then((response) {
+      expect(response.statusCode, equals(404));
+    });
+  });
+
+  test("runs a handler for an exact URL", () {
+    var request = new shelf.Request("GET", Uri.parse("http://localhost/foo"));
+    handler.add("foo", expectAsync((request) {
+      expect(request.handlerPath, equals('/foo'));
+      expect(request.url.path, isEmpty);
+      return new shelf.Response.ok("good job!");
+    }));
+
+    return _handle(request).then((response) {
+      expect(response.statusCode, equals(200));
+      expect(response.readAsString(), completion(equals("good job!")));
+    });
+  });
+
+  test("runs a handler for a suffix", () {
+    var request = new shelf.Request(
+        "GET", Uri.parse("http://localhost/foo/bar"));
+    handler.add("foo", expectAsync((request) {
+      expect(request.handlerPath, equals('/foo/'));
+      expect(request.url.path, 'bar');
+      return new shelf.Response.ok("good job!");
+    }));
+
+    return _handle(request).then((response) {
+      expect(response.statusCode, equals(200));
+      expect(response.readAsString(), completion(equals("good job!")));
+    });
+  });
+
+  test("runs the longest matching handler", () {
+    var request = new shelf.Request(
+        "GET", Uri.parse("http://localhost/foo/bar/baz"));
+
+    handler.add("foo", expectAsync((_) {}, count: 0));
+    handler.add("foo/bar", expectAsync((request) {
+      expect(request.handlerPath, equals('/foo/bar/'));
+      expect(request.url.path, 'baz');
+      return new shelf.Response.ok("good job!");
+    }));
+    handler.add("foo/bar/baz/bang", expectAsync((_) {}, count: 0));
+
+    return _handle(request).then((response) {
+      expect(response.statusCode, equals(200));
+      expect(response.readAsString(), completion(equals("good job!")));
+    });
+  });
+}