Adds logic to automatically infer specification type during WebDriver creation. (#172)

* Adds automated spec inference ability during WebDriver creation.

* Adds ability to define spec during WebDriver creation. Sets defaults to W3C for FireFox and JSONWire for Chrome.

* Adds test for spec inference. Updates logic for reusing exisiting sessions for W3C spec.

* Add error messages.

* Updates spec inference test with different exception types.
diff --git a/lib/src/sync/json_wire_spec/exception.dart b/lib/src/sync/json_wire_spec/exception.dart
index 0c0b035..b1082f6 100644
--- a/lib/src/sync/json_wire_spec/exception.dart
+++ b/lib/src/sync/json_wire_spec/exception.dart
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-library webdriver.exception;
+library webdriver.json_exception;
 
 import '../exception.dart';
 
diff --git a/lib/src/sync/spec_inference_response_processor.dart b/lib/src/sync/spec_inference_response_processor.dart
new file mode 100644
index 0000000..b5cf656
--- /dev/null
+++ b/lib/src/sync/spec_inference_response_processor.dart
@@ -0,0 +1,51 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:convert' show JSON;
+
+import 'package:sync_http/sync_http.dart';
+import 'package:webdriver/sync_core.dart';
+
+/// Inferred spec and sessionId from a session creation request.
+class InferredResponse {
+  final String sessionId;
+
+  final WebDriverSpec spec;
+
+  InferredResponse(this.sessionId, this.spec);
+}
+
+/// Infers the spec during session creation.
+dynamic inferSessionResponseSpec(SyncHttpClientResponse response, bool _) {
+  Map responseBody;
+  try {
+    responseBody = JSON.decode(response.body);
+  } catch (e) {}
+
+  // TODO(staats): create more description error messages.
+  if (response.statusCode < 200 || response.statusCode > 299) {
+    throw 'Response code: ${response.statusCode}';
+  }
+
+  // JSON responses have multiple keys.
+  if (responseBody.keys.length > 1) {
+    return new InferredResponse(
+        responseBody['sessionId'], WebDriverSpec.JsonWire);
+    // W3C responses have only one key, value.
+  } else if (responseBody.keys.length == 1) {
+    return new InferredResponse(
+        responseBody['value']['sessionId'], WebDriverSpec.W3c);
+  }
+  throw 'Could not infer spec type; number of keys: ${responseBody.keys}';
+}
diff --git a/lib/src/sync/w3c_spec/exception.dart b/lib/src/sync/w3c_spec/exception.dart
index 8fee62c..6bf474e 100644
--- a/lib/src/sync/w3c_spec/exception.dart
+++ b/lib/src/sync/w3c_spec/exception.dart
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-library webdriver.exception;
+library webdriver.w3c_exception;
 
 import '../exception.dart';
 
diff --git a/lib/sync_core.dart b/lib/sync_core.dart
index 45dd702..4024c45 100644
--- a/lib/sync_core.dart
+++ b/lib/sync_core.dart
@@ -20,6 +20,7 @@
 import 'package:webdriver/src/sync/web_driver.dart' show WebDriver;
 
 import 'package:webdriver/src/sync/command_processor.dart';
+import 'package:webdriver/src/sync/spec_inference_response_processor.dart';
 import 'package:webdriver/src/sync/json_wire_spec/response_processor.dart';
 import 'package:webdriver/src/sync/json_wire_spec/web_driver.dart' as jwire;
 import 'package:webdriver/src/sync/w3c_spec/response_processor.dart';
@@ -79,7 +80,12 @@
       return new w3c.W3cWebDriver(processor, uri, response['sessionId'],
           new UnmodifiableMapView(response['value'] as Map<String, dynamic>));
     case WebDriverSpec.Auto:
-      throw 'Not yet supported!';
+      final response =
+          new SyncHttpCommandProcessor(processor: inferSessionResponseSpec)
+              .post(uri.resolve('session'), {'desiredCapabilities': desired},
+                  value: true) as InferredResponse;
+      return fromExistingSession(response.sessionId,
+          uri: uri, spec: response.spec);
     default:
       throw 'Not yet supported!'; // Impossible.
   }
@@ -102,12 +108,10 @@
     case WebDriverSpec.W3c:
       final processor =
           new SyncHttpCommandProcessor(processor: processW3cResponse);
-      final response = processor.get(uri.resolve('session/$sessionId'))
-          as Map<String, dynamic>;
       return new w3c.W3cWebDriver(
-          processor, uri, sessionId, new UnmodifiableMapView(response));
+          processor, uri, sessionId, new Map<String, dynamic>());
     case WebDriverSpec.Auto:
-      throw 'Not yet supported!';
+      throw 'Not supported!';
     default:
       throw 'Not yet supported!'; // Impossible.
   }
diff --git a/test/sync/spec_inference_test.dart b/test/sync/spec_inference_test.dart
new file mode 100644
index 0000000..4f7c608
--- /dev/null
+++ b/test/sync/spec_inference_test.dart
@@ -0,0 +1,64 @@
+// Copyright 2017 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//    http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+@TestOn("vm")
+library webdriver.spec_inference_test;
+
+import 'package:test/test.dart';
+import 'package:webdriver/src/sync/json_wire_spec/exception.dart' as json;
+import 'package:webdriver/src/sync/w3c_spec/exception.dart' as w3c;
+import 'package:webdriver/sync_core.dart';
+
+import 'sync_io_config.dart' as config;
+
+void main() {
+  group('Spec inference', () {
+    WebDriver driver;
+
+    setUp(() {});
+
+    tearDown(() {
+      if (driver != null) {
+        driver.quit();
+      }
+      driver = null;
+    });
+
+    test('chrome works', () {
+      driver = config.createChromeTestDriver(spec: WebDriverSpec.Auto);
+      driver.get(config.testPagePath);
+      final button = driver.findElement(const By.tagName('button'));
+      try {
+        button.findElement(const By.tagName('tr'));
+        throw 'Expected NoSuchElementException';
+      } catch(e) {
+        expect(e, new isInstanceOf<json.NoSuchElementException>());
+        expect(e.toString(), contains('Unable to locate element'));
+      }
+    });
+
+    test('firefox work', () {
+      driver = config.createFirefoxTestDriver(spec: WebDriverSpec.Auto);
+      driver.get(config.testPagePath);
+      final button = driver.findElement(const By.tagName('button'));
+      try {
+        button.findElement(const By.tagName('tr'));
+        throw 'Expected W3cWebDriverException';
+      } catch(e) {
+        expect(e, new isInstanceOf<w3c.W3cWebDriverException>());
+        expect(e.toString(), contains('Unable to locate element'));
+      }
+    });
+  }, timeout: new Timeout(new Duration(minutes: 2)));
+}
diff --git a/test/sync/sync_io_config.dart b/test/sync/sync_io_config.dart
index 8e2e9e9..85a3797 100644
--- a/test/sync/sync_io_config.dart
+++ b/test/sync/sync_io_config.dart
@@ -29,18 +29,20 @@
 final Uri _defaultFirefoxUri = Uri.parse('http://127.0.0.1:4445/');
 
 WebDriver createFirefoxTestDriver(
-    {Map<String, dynamic> additionalCapabilities}) {
+    {Map<String, dynamic> additionalCapabilities,
+    WebDriverSpec spec: WebDriverSpec.W3c}) {
   final capabilities = Capabilities.firefox;
 
   if (additionalCapabilities != null) {
     capabilities.addAll(additionalCapabilities);
   }
   return createDriver(
-      uri: _defaultFirefoxUri, desired: capabilities, spec: WebDriverSpec.W3c);
+      uri: _defaultFirefoxUri, desired: capabilities, spec: spec);
 }
 
 WebDriver createChromeTestDriver(
-    {Map<String, dynamic> additionalCapabilities}) {
+    {Map<String, dynamic> additionalCapabilities,
+    WebDriverSpec spec: WebDriverSpec.JsonWire}) {
   var capabilities = Capabilities.chrome;
   Map env = Platform.environment;
 
@@ -62,5 +64,6 @@
     capabilities.addAll(additionalCapabilities);
   }
 
-  return createDriver(uri: _defaultChromeUri, desired: additionalCapabilities);
+  return createDriver(
+      uri: _defaultChromeUri, desired: additionalCapabilities, spec: spec);
 }