summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/bluetooth/script-tests
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/bluetooth/script-tests')
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/base_test_js.template7
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/characteristic/characteristic-is-removed.js24
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/characteristic/descriptor-get-same-object.js32
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/characteristic/service-is-removed.js20
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/descriptor/service-is-removed.js18
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-before.js22
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-during-error.js22
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-during-success.js23
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/disconnect-discovery-timeout.js42
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/disconnect-invalidates-objects.js39
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/disconnected-device.js20
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/discovery-complete-no-permission-absent-service.js25
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/discovery-complete-service-not-found.js16
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/garbage-collection-ran-during-error.js25
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/garbage-collection-ran-during-success.js24
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/get-different-service-after-reconnection.js35
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/get-same-object.js33
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/invalid-service-name.js22
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/no-permission-absent-service.js23
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/no-permission-for-any-service.js17
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/no-permission-present-service.js22
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/server/service-not-found.js16
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/service/blocklisted-characteristic.js19
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/service/characteristic-not-found.js15
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/service/garbage-collection-ran-during-error.js24
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/service/get-same-object.js24
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/service/invalid-characteristic-name.js23
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/service/reconnect-during.js36
-rw-r--r--testing/web-platform/tests/bluetooth/script-tests/service/service-is-removed.js20
29 files changed, 688 insertions, 0 deletions
diff --git a/testing/web-platform/tests/bluetooth/script-tests/base_test_js.template b/testing/web-platform/tests/bluetooth/script-tests/base_test_js.template
new file mode 100644
index 0000000000..04c7a70ba4
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/base_test_js.template
@@ -0,0 +1,7 @@
+// META: script=/resources/testdriver.js
+// META: script=/resources/testdriver-vendor.js
+// META: script=/common/gc.js
+// META: script=/bluetooth/resources/bluetooth-test.js
+// META: script=/bluetooth/resources/bluetooth-fake-devices.js
+// Generated by //third_party/WebKit/LayoutTests/bluetooth/generate.py
+TEST
diff --git a/testing/web-platform/tests/bluetooth/script-tests/characteristic/characteristic-is-removed.js b/testing/web-platform/tests/bluetooth/script-tests/characteristic/characteristic-is-removed.js
new file mode 100644
index 0000000000..48aaec3e93
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/characteristic/characteristic-is-removed.js
@@ -0,0 +1,24 @@
+'use strict';
+const test_desc = 'Characteristic gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Characteristic no longer exists.',
+ 'InvalidStateError');
+let fake_peripheral, characteristic, fake_characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+ .then(_ => ({fake_peripheral, characteristic, fake_characteristic} = _))
+ .then(() => characteristic.getDescriptor(user_description.name))
+ .then(() => null, (e) => assert_unreached('Caught error unexpectedly.', e))
+ .then(() => fake_characteristic.remove())
+ .then(() => fake_peripheral.simulateGATTServicesChanged())
+ .then(() => assert_promise_rejects_with_message(
+ characteristic.CALLS([
+ getDescriptor(user_description.name)|
+ getDescriptors(user_description.name)[UUID]|
+ getDescriptors()|
+ readValue()|
+ writeValue(new Uint8Array(1))|
+ writeValueWithResponse(new Uint8Array(1))|
+ writeValueWithoutResponse(new Uint8Array(1))|
+ startNotifications()
+ ]), expected)),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/characteristic/descriptor-get-same-object.js b/testing/web-platform/tests/bluetooth/script-tests/characteristic/descriptor-get-same-object.js
new file mode 100644
index 0000000000..4e6bc3519b
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/characteristic/descriptor-get-same-object.js
@@ -0,0 +1,32 @@
+'use strict';
+const test_desc = 'Calls to FUNCTION_NAME should return the same object.';
+let characteristic;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+ .then(_ => ({characteristic} = _))
+ .then(() => Promise.all([
+ characteristic.CALLS([
+ getDescriptor(user_description.alias)|
+ getDescriptors(user_description.alias)
+ ]),
+ characteristic.FUNCTION_NAME(user_description.name),
+ characteristic.FUNCTION_NAME(user_description.uuid)
+ ]))
+ .then(descriptors_arrays => {
+ assert_true(descriptors_arrays.length > 0)
+
+ // Convert to arrays if necessary.
+ for (let i = 0; i < descriptors_arrays.length; i++) {
+ descriptors_arrays[i] = [].concat(descriptors_arrays[i]);
+ }
+
+ for (let i = 1; i < descriptors_arrays.length; i++) {
+ assert_equals(descriptors_arrays[0].length,
+ descriptors_arrays[i].length);
+ }
+
+ let base_set = new Set(descriptors_arrays[0]);
+ for (let descriptors of descriptors_arrays) {
+ descriptors.forEach(descriptor => assert_true(base_set.has(descriptor)));
+ }
+ }), test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/characteristic/service-is-removed.js b/testing/web-platform/tests/bluetooth/script-tests/characteristic/service-is-removed.js
new file mode 100644
index 0000000000..2f5824082b
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/characteristic/service-is-removed.js
@@ -0,0 +1,20 @@
+// TODO(https://crbug.com/672127) Use this test case to test the rest of
+// characteristic functions.
+'use strict';
+const test_desc = 'Service is removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+ 'InvalidStateError');
+let characteristic, fake_peripheral, fake_service;
+
+bluetooth_test(() => getMeasurementIntervalCharacteristic()
+ .then(_ => ({characteristic, fake_peripheral, fake_service} = _))
+ .then(() => fake_service.remove())
+ .then(() => fake_peripheral.simulateGATTServicesChanged())
+ .then(() => assert_promise_rejects_with_message(
+ characteristic.CALLS([
+ getDescriptor(user_description.name)|
+ getDescriptors(user_description.uuid)[UUID]|
+ getDescriptors(user_description.name)]),
+ expected,
+ 'Service got removed.')),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/descriptor/service-is-removed.js b/testing/web-platform/tests/bluetooth/script-tests/descriptor/service-is-removed.js
new file mode 100644
index 0000000000..5373364399
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/descriptor/service-is-removed.js
@@ -0,0 +1,18 @@
+'use strict';
+const test_desc = 'Service gets removed. Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+ 'InvalidStateError');
+let descriptor, fake_peripheral, fake_service;
+
+bluetooth_test(() => getUserDescriptionDescriptor()
+ .then(_ => ({descriptor, fake_peripheral, fake_service} = _))
+ .then(() => fake_service.remove())
+ .then(() => fake_peripheral.simulateGATTServicesChanged())
+ .then(() => assert_promise_rejects_with_message(
+ descriptor.CALLS([
+ readValue()|
+ writeValue(new ArrayBuffer(1 /* length */))
+ ]),
+ expected,
+ 'Service got removed.')),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-before.js b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-before.js
new file mode 100644
index 0000000000..57704ee299
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-before.js
@@ -0,0 +1,22 @@
+'use strict';
+const test_desc = 'disconnect() called before FUNCTION_NAME. ' +
+ 'Reject with NetworkError.';
+const expected = new DOMException(
+ 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+ 'first with `device.gatt.connect`.',
+ 'NetworkError');
+let device;
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({
+ filters: [{services: ['health_thermometer']}],
+ optionalServices: ['generic_access']
+ })
+ .then(_ => ({device} = _))
+ .then(() => device.gatt.disconnect())
+ .then(() => assert_promise_rejects_with_message(
+ device.gatt.CALLS([
+ getPrimaryService('health_thermometer')|
+ getPrimaryServices()|
+ getPrimaryServices('health_thermometer')[UUID]]),
+ expected)),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-during-error.js b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-during-error.js
new file mode 100644
index 0000000000..edabb07bcc
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-during-error.js
@@ -0,0 +1,22 @@
+'use strict';
+const test_desc = 'disconnect() called during a FUNCTION_NAME ' +
+ 'call that fails. Reject with NetworkError.';
+const expected = new DOMException(
+ 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+ 'first with `device.gatt.connect`.', 'NetworkError');
+let device;
+
+bluetooth_test(() => getEmptyHealthThermometerDevice()
+ .then(_ => ({device} = _))
+ .then(() => {
+ let promise = assert_promise_rejects_with_message(
+ device.gatt.CALLS([
+ getPrimaryService('health_thermometer')|
+ getPrimaryServices()|
+ getPrimaryServices('health_thermometer')[UUID]
+ ]),
+ expected)
+ device.gatt.disconnect();
+ return promise;
+ }),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-during-success.js b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-during-success.js
new file mode 100644
index 0000000000..84157a0693
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-called-during-success.js
@@ -0,0 +1,23 @@
+'use strict';
+const test_desc = 'disconnect() called during a FUNCTION_NAME call that ' +
+ 'succeeds. Reject with NetworkError.';
+const expected = new DOMException(
+ 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+ 'first with `device.gatt.connect`.',
+ 'NetworkError');
+
+bluetooth_test(() => getHealthThermometerDevice({
+ filters: [{services: ['health_thermometer']}],
+ optionalServices: ['generic_access']
+ })
+ .then(({device}) => {
+ let promise = assert_promise_rejects_with_message(
+ device.gatt.CALLS([
+ getPrimaryService('health_thermometer')|
+ getPrimaryServices()|
+ getPrimaryServices('health_thermometer')[UUID]
+ ]),
+ expected);
+ device.gatt.disconnect();
+ return promise;
+ }), test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-discovery-timeout.js b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-discovery-timeout.js
new file mode 100644
index 0000000000..718e290950
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-discovery-timeout.js
@@ -0,0 +1,42 @@
+'use strict';
+const test_desc =
+ 'Calls to FUNCTION_NAME when device disconnects and discovery' +
+ ' times out should reject promise rather than get stuck.';
+let device;
+
+bluetooth_test(
+ async (t) => {
+ let {device, fake_peripheral} =
+ await getConnectedHealthThermometerDevice({
+ filters: [{services: ['health_thermometer']}],
+ optionalServices: ['generic_access']
+ });
+
+ await fake_peripheral.setNextGATTDiscoveryResponse({
+ code: HCI_CONNECTION_TIMEOUT,
+ });
+ await Promise.all([
+ fake_peripheral.simulateGATTDisconnection({
+ code: HCI_SUCCESS,
+ }),
+ // Using promise_rejects_dom here rather than
+ // assert_promise_rejects_with_message as the race between
+ // simulateGATTDisconnection and getPrimaryServices might end up giving
+ // slightly different exception message (i.e has "Failed to execute ...
+ // on
+ // ... " prefix when disconnected state is reflected on the renderer
+ // side). The point of the test is no matter how race between them, the
+ // promise will be rejected as opposed to get stuck.
+ promise_rejects_dom(t, 'NetworkError', device.gatt.CALLS([
+ getPrimaryService('health_thermometer') | getPrimaryServices() |
+ getPrimaryServices('health_thermometer')[UUID]
+ ])),
+ ]);
+ },
+ test_desc, '',
+ // As specified above there is a race condition between
+ // simulateGATTDisconnection and getPrimaryServices, the artificial
+ // GATTDiscoveryResponse might not be consumed in case
+ // simulateGATTDisconnection happens first. As a result explicitly skip
+ // all response consumed validation at the end of the test.
+ /*validate_response_consumed=*/ false);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-invalidates-objects.js b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-invalidates-objects.js
new file mode 100644
index 0000000000..995fda3441
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/disconnect-invalidates-objects.js
@@ -0,0 +1,39 @@
+'use strict';
+const test_desc = 'Calls on services after we disconnect and connect again. '+
+ 'Should reject with InvalidStateError.';
+let device, services;
+
+bluetooth_test(() => getHealthThermometerDevice({
+ filters: [{services: ['health_thermometer']}]
+ })
+ .then(_ => ({device} = _))
+ .then(() => device.gatt.CALLS([
+ getPrimaryService('health_thermometer')|
+ getPrimaryServices()|
+ getPrimaryServices('health_thermometer')[UUID]]))
+ // Convert to array if necessary.
+ .then(s => services = [].concat(s))
+ .then(() => device.gatt.disconnect())
+ .then(() => device.gatt.connect())
+ .then(() => {
+ let promises = Promise.resolve();
+ for (let service of services) {
+ let error = new DOMException(
+ `Service with UUID ${service.uuid} is no longer valid. Remember ` +
+ `to retrieve the service again after reconnecting.`,
+ 'InvalidStateError');
+ promises = promises.then(() =>
+ assert_promise_rejects_with_message(
+ service.getCharacteristic('measurement_interval'),
+ error));
+ promises = promises.then(() =>
+ assert_promise_rejects_with_message(
+ service.getCharacteristics(),
+ error));
+ promises = promises.then(() =>
+ assert_promise_rejects_with_message(
+ service.getCharacteristics('measurement_interval'),
+ error));
+ }
+ return promises;
+ }), test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/disconnected-device.js b/testing/web-platform/tests/bluetooth/script-tests/server/disconnected-device.js
new file mode 100644
index 0000000000..2b6011642b
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/disconnected-device.js
@@ -0,0 +1,20 @@
+'use strict';
+const test_desc = 'FUNCTION_NAME called before connecting. Reject with ' +
+ 'NetworkError.';
+const expected = new DOMException(
+ 'GATT Server is disconnected. Cannot retrieve services. (Re)connect ' +
+ 'first with `device.gatt.connect`.',
+ 'NetworkError');
+
+bluetooth_test(() => getDiscoveredHealthThermometerDevice({
+ filters: [{services: ['health_thermometer']}],
+ optionalServices: ['generic_access']
+ })
+ .then(({device}) => assert_promise_rejects_with_message(
+ device.gatt.CALLS([
+ getPrimaryService('health_thermometer')|
+ getPrimaryServices()|
+ getPrimaryServices('health_thermometer')[UUID]
+ ]),
+ expected)),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/discovery-complete-no-permission-absent-service.js b/testing/web-platform/tests/bluetooth/script-tests/server/discovery-complete-no-permission-absent-service.js
new file mode 100644
index 0000000000..e9e972359a
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/discovery-complete-no-permission-absent-service.js
@@ -0,0 +1,25 @@
+'use strict';
+const test_desc = 'Request for absent service without permission. Should ' +
+ 'Reject with SecurityError even if services have been discovered already.';
+const expected = new DOMException(
+ 'Origin is not allowed to access the service. Tip: Add the service ' +
+ 'UUID to \'optionalServices\' in requestDevice() options. ' +
+ 'https://goo.gl/HxfxSQ',
+ 'SecurityError');
+let device;
+
+bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({
+ filters: [{services: ['health_thermometer']}]
+ })
+ .then(_ => ({device} = _))
+ .then(() => Promise.all([
+ assert_promise_rejects_with_message(
+ device.gatt.CALLS([
+ getPrimaryService(glucose.alias)|
+ getPrimaryServices(glucose.alias)[UUID]
+ ]), expected),
+ assert_promise_rejects_with_message(
+ device.gatt.FUNCTION_NAME(glucose.name), expected),
+ assert_promise_rejects_with_message(
+ device.gatt.FUNCTION_NAME(glucose.uuid), expected)])),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/discovery-complete-service-not-found.js b/testing/web-platform/tests/bluetooth/script-tests/server/discovery-complete-service-not-found.js
new file mode 100644
index 0000000000..6b745d7e2a
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/discovery-complete-service-not-found.js
@@ -0,0 +1,16 @@
+'use strict';
+const test_desc = 'Request for absent service. Must reject with ' +
+ 'NotFoundError even when the services have previously been discovered.';
+
+bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({
+ filters: [{services: ['health_thermometer']}],
+ optionalServices: ['glucose']})
+ .then(({device}) => assert_promise_rejects_with_message(
+ device.gatt.CALLS([
+ getPrimaryService('glucose')|
+ getPrimaryServices('glucose')[UUID]
+ ]),
+ new DOMException(
+ `No Services matching UUID ${glucose.uuid} found in Device.`,
+ 'NotFoundError'))),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/garbage-collection-ran-during-error.js b/testing/web-platform/tests/bluetooth/script-tests/server/garbage-collection-ran-during-error.js
new file mode 100644
index 0000000000..cf508a928e
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/garbage-collection-ran-during-error.js
@@ -0,0 +1,25 @@
+'use strict';
+const test_desc = 'Garbage Collection ran during a FUNCTION_NAME ' +
+ 'call that failed. Should not crash.'
+const expected = new DOMException(
+ 'GATT Server is disconnected. Cannot retrieve services. (Re)connect first ' +
+ 'with `device.gatt.connect`.',
+ 'NetworkError');
+let promise;
+
+bluetooth_test(() => getEmptyHealthThermometerDevice()
+ .then(({device}) => {
+ promise = assert_promise_rejects_with_message(
+ device.gatt.CALLS([
+ getPrimaryService('health_thermometer')|
+ getPrimaryServices()|
+ getPrimaryServices('health_thermometer')[UUID]
+ ]),
+ expected);
+ // Disconnect called to clear attributeInstanceMap and allow the
+ // object to get garbage collected.
+ device.gatt.disconnect();
+ return garbageCollect();
+ })
+ .then(() => promise),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/garbage-collection-ran-during-success.js b/testing/web-platform/tests/bluetooth/script-tests/server/garbage-collection-ran-during-success.js
new file mode 100644
index 0000000000..bb472fcca4
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/garbage-collection-ran-during-success.js
@@ -0,0 +1,24 @@
+'use strict';
+const test_desc = 'Garbage Collection ran during a FUNCTION_NAME call that ' +
+ 'succeeds. Should not crash.';
+const expected = new DOMException(
+ 'GATT Server is disconnected. Cannot retrieve services. ' +
+ '(Re)connect first with `device.gatt.connect`.',
+ 'NetworkError');
+let promise;
+
+bluetooth_test(() => getHealthThermometerDevice({
+ filters: [{services: ['health_thermometer']}]
+ })
+ .then(({device}) => {
+ promise = assert_promise_rejects_with_message(
+ device.gatt.CALLS([
+ getPrimaryService('health_thermometer') |
+ getPrimaryServices() |
+ getPrimaryServices('health_thermometer')[UUID]]),
+ expected);
+ device.gatt.disconnect();
+ return garbageCollect();
+ })
+ .then(() => promise),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/get-different-service-after-reconnection.js b/testing/web-platform/tests/bluetooth/script-tests/server/get-different-service-after-reconnection.js
new file mode 100644
index 0000000000..e72128a76f
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/get-different-service-after-reconnection.js
@@ -0,0 +1,35 @@
+'use strict';
+const test_desc = 'Calls to FUNCTION_NAME after a disconnection should return ' +
+ 'a different object.';
+let device, services_first_connection, services_second_connection;
+
+bluetooth_test(() => getHealthThermometerDevice({
+ filters: [{services: ['health_thermometer']}],
+ optionalServices: ['generic_access']
+ })
+ .then(_ => ({device} = _))
+ .then(() => device.gatt.CALLS([
+ getPrimaryService('health_thermometer')|
+ getPrimaryServices()|
+ getPrimaryServices('health_thermometer')[UUID]]))
+ .then(services => services_first_connection = services)
+ .then(() => device.gatt.disconnect())
+ .then(() => device.gatt.connect())
+ .then(() => device.gatt.PREVIOUS_CALL)
+ .then(services => services_second_connection = services)
+ .then(() => {
+ // Convert to arrays if necessary.
+ services_first_connection = [].concat(services_first_connection);
+ services_second_connection = [].concat(services_second_connection);
+
+ assert_equals(services_first_connection.length,
+ services_second_connection.length);
+
+ let first_connection_set = new Set(services_first_connection);
+ let second_connection_set = new Set(services_second_connection);
+
+ // The two sets should be disjoint.
+ let common_services = services_first_connection.filter(
+ val => second_connection_set.has(val));
+ assert_equals(common_services.length, 0);
+ }), test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/get-same-object.js b/testing/web-platform/tests/bluetooth/script-tests/server/get-same-object.js
new file mode 100644
index 0000000000..3b3bdd19d2
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/get-same-object.js
@@ -0,0 +1,33 @@
+'use strict';
+const test_desc = 'Calls to FUNCTION_NAME should return the same object.';
+let device;
+
+bluetooth_test(() => getHealthThermometerDevice({
+ filters: [{services: ['health_thermometer']}],
+ optionalServices: ['generic_access']})
+ .then(({device}) => Promise.all([
+ device.gatt.CALLS([
+ getPrimaryService('health_thermometer')|
+ getPrimaryServices()|
+ getPrimaryServices('health_thermometer')[UUID]]),
+ device.gatt.PREVIOUS_CALL]))
+ .then(([services_first_call, services_second_call]) => {
+ // Convert to arrays if necessary.
+ services_first_call = [].concat(services_first_call);
+ services_second_call = [].concat(services_second_call);
+
+ assert_equals(services_first_call.length, services_second_call.length);
+
+ let first_call_set = new Set(services_first_call);
+ assert_equals(services_first_call.length, first_call_set.size);
+ let second_call_set = new Set(services_second_call);
+ assert_equals(services_second_call.length, second_call_set.size);
+
+ services_first_call.forEach(service => {
+ assert_true(second_call_set.has(service))
+ });
+
+ services_second_call.forEach(service => {
+ assert_true(first_call_set.has(service));
+ });
+ }), test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/invalid-service-name.js b/testing/web-platform/tests/bluetooth/script-tests/server/invalid-service-name.js
new file mode 100644
index 0000000000..52cbb24f4a
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/invalid-service-name.js
@@ -0,0 +1,22 @@
+'use strict';
+const test_desc = 'Wrong Service name. Reject with TypeError.';
+const expected = new DOMException(
+ "Failed to execute 'FUNCTION_NAME' on " +
+ "'BluetoothRemoteGATTServer': Invalid Service name: " +
+ "'wrong_name'. It must be a valid UUID alias (e.g. 0x1234), " +
+ "UUID (lowercase hex characters e.g. " +
+ "'00001234-0000-1000-8000-00805f9b34fb'), " +
+ "or recognized standard name from " +
+ "https://www.bluetooth.com/specifications/gatt/services" +
+ " e.g. 'alert_notification'.",
+ 'TypeError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice()
+ .then(({device}) => assert_promise_rejects_with_message(
+ device.gatt.CALLS([
+ getPrimaryService('wrong_name')|
+ getPrimaryServices('wrong_name')
+ ]),
+ expected,
+ 'Wrong Service name passed.')),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-absent-service.js b/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-absent-service.js
new file mode 100644
index 0000000000..200dab3e93
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-absent-service.js
@@ -0,0 +1,23 @@
+'use strict';
+const test_desc = 'Request for absent service without permission. ' +
+ 'Reject with SecurityError.';
+const expected = new DOMException(
+ 'Origin is not allowed to access the service. Tip: Add the service UUID ' +
+ 'to \'optionalServices\' in requestDevice() options. ' +
+ 'https://goo.gl/HxfxSQ',
+ 'SecurityError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({
+ filters: [{services: ['health_thermometer']}]
+ })
+ .then(({device}) => Promise.all([
+ assert_promise_rejects_with_message(
+ device.gatt.CALLS([
+ getPrimaryService(glucose.alias)|
+ getPrimaryServices(glucose.alias)[UUID]
+ ]), expected),
+ assert_promise_rejects_with_message(
+ device.gatt.FUNCTION_NAME(glucose.name), expected),
+ assert_promise_rejects_with_message(
+ device.gatt.FUNCTION_NAME(glucose.uuid), expected)])),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-for-any-service.js b/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-for-any-service.js
new file mode 100644
index 0000000000..60e3ef0080
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-for-any-service.js
@@ -0,0 +1,17 @@
+'use strict';
+const test_desc = 'Request for present service without permission to access ' +
+ 'any service. Reject with SecurityError.';
+const expected = new DOMException(
+ 'Origin is not allowed to access any service. Tip: Add the service ' +
+ 'UUID to \'optionalServices\' in requestDevice() options. ' +
+ 'https://goo.gl/HxfxSQ',
+ 'SecurityError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({acceptAllDevices: true})
+ .then(({device}) => assert_promise_rejects_with_message(
+ device.gatt.CALLS([
+ getPrimaryService('heart_rate')|
+ getPrimaryServices()|
+ getPrimaryServices('heart_rate')[UUID]]),
+ expected)),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-present-service.js b/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-present-service.js
new file mode 100644
index 0000000000..3257410685
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/no-permission-present-service.js
@@ -0,0 +1,22 @@
+'use strict';
+const test_desc = 'Request for present service without permission. ' +
+ 'Reject with SecurityError.';
+const expected = new DOMException(
+ 'Origin is not allowed to access the service. Tip: Add the service UUID ' +
+ 'to \'optionalServices\' in requestDevice() options. https://goo.gl/HxfxSQ',
+ 'SecurityError');
+
+bluetooth_test(() => getConnectedHealthThermometerDevice({
+ filters: [{services: ['health_thermometer']}]
+ })
+ .then(({device}) => Promise.all([
+ assert_promise_rejects_with_message(
+ device.gatt.CALLS([
+ getPrimaryService(generic_access.alias)|
+ getPrimaryServices(generic_access.alias)[UUID]
+ ]), expected),
+ assert_promise_rejects_with_message(
+ device.gatt.FUNCTION_NAME(generic_access.name), expected),
+ assert_promise_rejects_with_message(
+ device.gatt.FUNCTION_NAME(generic_access.uuid), expected)])),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/server/service-not-found.js b/testing/web-platform/tests/bluetooth/script-tests/server/service-not-found.js
new file mode 100644
index 0000000000..0fd2dace78
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/server/service-not-found.js
@@ -0,0 +1,16 @@
+'use strict';
+const test_desc = 'Request for absent service. Reject with NotFoundError.';
+
+bluetooth_test(() => getHealthThermometerDevice({
+ filters: [{services: ['health_thermometer']}],
+ optionalServices: ['glucose']
+ })
+ .then(({device}) => assert_promise_rejects_with_message(
+ device.gatt.CALLS([
+ getPrimaryService('glucose')|
+ getPrimaryServices('glucose')[UUID]
+ ]),
+ new DOMException(
+ `No Services matching UUID ${glucose.uuid} found in Device.`,
+ 'NotFoundError'))),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/service/blocklisted-characteristic.js b/testing/web-platform/tests/bluetooth/script-tests/service/blocklisted-characteristic.js
new file mode 100644
index 0000000000..b26f039a70
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/service/blocklisted-characteristic.js
@@ -0,0 +1,19 @@
+'use strict';
+const test_desc = 'Serial Number String characteristic is blocklisted. ' +
+ 'Should reject with SecurityError.';
+const expected = new DOMException(
+ 'getCharacteristic(s) called with blocklisted UUID. https://goo.gl/4NeimX',
+ 'SecurityError');
+
+bluetooth_test(() => getHIDDevice({
+ filters: [{services: ['device_information']}]
+})
+ .then(({device}) => device.gatt.getPrimaryService('device_information'))
+ .then(service => assert_promise_rejects_with_message(
+ service.CALLS([
+ getCharacteristic('serial_number_string')|
+ getCharacteristics('serial_number_string')[UUID]
+ ]),
+ expected,
+ 'Serial Number String characteristic is blocklisted.')),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/service/characteristic-not-found.js b/testing/web-platform/tests/bluetooth/script-tests/service/characteristic-not-found.js
new file mode 100644
index 0000000000..366e046774
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/service/characteristic-not-found.js
@@ -0,0 +1,15 @@
+'use strict';
+const test_desc = 'Request for absent characteristics with UUID. ' +
+ 'Reject with NotFoundError.';
+
+bluetooth_test(() => getEmptyHealthThermometerService()
+ .then(({service}) => assert_promise_rejects_with_message(
+ service.CALLS([
+ getCharacteristic('battery_level')|
+ getCharacteristics('battery_level')[UUID]
+ ]),
+ new DOMException(
+ `No Characteristics matching UUID ${battery_level.uuid} found ` +
+ `in Service with UUID ${health_thermometer.uuid}.`,
+ 'NotFoundError'))),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/service/garbage-collection-ran-during-error.js b/testing/web-platform/tests/bluetooth/script-tests/service/garbage-collection-ran-during-error.js
new file mode 100644
index 0000000000..7ed4aaa962
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/service/garbage-collection-ran-during-error.js
@@ -0,0 +1,24 @@
+'use strict';
+const test_desc = 'Garbage Collection ran during FUNCTION_NAME ' +
+ 'call that fails. Should not crash';
+const expected = new DOMException(
+ 'GATT Server is disconnected. Cannot retrieve characteristics. ' +
+ '(Re)connect first with `device.gatt.connect`.',
+ 'NetworkError');
+let promise;
+
+bluetooth_test(() => getHealthThermometerService()
+ .then(({service}) => {
+ promise = assert_promise_rejects_with_message(
+ service.CALLS([
+ getCharacteristic('measurement_interval')|
+ getCharacteristics()|
+ getCharacteristics('measurement_interval')[UUID]
+ ]), expected);
+ // Disconnect called to clear attributeInstanceMap and allow the object to
+ // get garbage collected.
+ service.device.gatt.disconnect();
+ })
+ .then(garbageCollect)
+ .then(() => promise),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/service/get-same-object.js b/testing/web-platform/tests/bluetooth/script-tests/service/get-same-object.js
new file mode 100644
index 0000000000..db9d740c83
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/service/get-same-object.js
@@ -0,0 +1,24 @@
+'use strict';
+const test_desc = 'Calls to FUNCTION_NAME should return the same object.';
+
+bluetooth_test(() => getHealthThermometerService()
+ .then(({service}) => Promise.all([
+ service.CALLS([
+ getCharacteristic('measurement_interval')|
+ getCharacteristics()|
+ getCharacteristics('measurement_interval')[UUID]]),
+ service.PREVIOUS_CALL]))
+ .then(([characteristics_first_call, characteristics_second_call]) => {
+ // Convert to arrays if necessary.
+ characteristics_first_call = [].concat(characteristics_first_call);
+ characteristics_second_call = [].concat(characteristics_second_call);
+
+ let first_call_set = new Set(characteristics_first_call);
+ assert_equals(characteristics_first_call.length, first_call_set.size);
+ let second_call_set = new Set(characteristics_second_call);
+ assert_equals(characteristics_second_call.length, second_call_set.size);
+
+ characteristics_first_call.forEach(characteristic => {
+ assert_true(second_call_set.has(characteristic));
+ });
+ }), test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/service/invalid-characteristic-name.js b/testing/web-platform/tests/bluetooth/script-tests/service/invalid-characteristic-name.js
new file mode 100644
index 0000000000..74cba7ec43
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/service/invalid-characteristic-name.js
@@ -0,0 +1,23 @@
+'use strict';
+const test_desc = 'Wrong Characteristic name. Reject with TypeError.';
+const expected = new DOMException(
+ "Failed to execute 'FUNCTION_NAME' on " +
+ "'BluetoothRemoteGATTService': Invalid Characteristic name: " +
+ "'wrong_name'. " +
+ "It must be a valid UUID alias (e.g. 0x1234), " +
+ "UUID (lowercase hex characters e.g. " +
+ "'00001234-0000-1000-8000-00805f9b34fb'), " +
+ "or recognized standard name from " +
+ "https://www.bluetooth.com/specifications/gatt/characteristics" +
+ " e.g. 'aerobic_heart_rate_lower_limit'.",
+ 'TypeError');
+
+bluetooth_test(() => getHealthThermometerService()
+ .then(({service}) => assert_promise_rejects_with_message(
+ service.CALLS([
+ getCharacteristic('wrong_name')|
+ getCharacteristics('wrong_name')
+ ]),
+ expected,
+ 'Wrong Characteristic name passed.')),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/service/reconnect-during.js b/testing/web-platform/tests/bluetooth/script-tests/service/reconnect-during.js
new file mode 100644
index 0000000000..cc71547ac2
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/service/reconnect-during.js
@@ -0,0 +1,36 @@
+'use strict';
+const test_desc = 'disconnect() and connect() called during ' +
+ 'FUNCTION_NAME. Reject with NetworkError.';
+const expected = new DOMException(
+ 'GATT Server is disconnected. Cannot retrieve characteristics. ' +
+ '(Re)connect first with `device.gatt.connect`.',
+ 'NetworkError');
+let device;
+
+bluetooth_test(() => getHealthThermometerDeviceWithServicesDiscovered({
+ filters: [{services: [health_thermometer.name]}],
+})
+ .then(_ => ({device} = _))
+ .then(() => device.gatt.getPrimaryService(health_thermometer.name))
+ .then(service => Promise.all([
+ // 1. Make a call to service.FUNCTION_NAME, while the service is still
+ // valid.
+ assert_promise_rejects_with_message(service.CALLS([
+ getCharacteristic(measurement_interval.name)|
+ getCharacteristics()|
+ getCharacteristics(measurement_interval.name)[UUID]
+ ]), expected),
+
+ // 2. disconnect() and connect before the initial call completes.
+ // This is accomplished by making the calls without waiting for the
+ // earlier promises to resolve.
+ // connect() guarantees on OS-level connection, but disconnect()
+ // only disconnects the current instance.
+ // getHealthThermometerDeviceWithServicesDiscovered holds another
+ // connection in an iframe, so disconnect() and connect() are certain to
+ // reconnect. However, disconnect() will invalidate the service object so
+ // the subsequent calls made to it will fail, even after reconnecting.
+ device.gatt.disconnect(),
+ device.gatt.connect()
+ ])),
+ test_desc);
diff --git a/testing/web-platform/tests/bluetooth/script-tests/service/service-is-removed.js b/testing/web-platform/tests/bluetooth/script-tests/service/service-is-removed.js
new file mode 100644
index 0000000000..aaf0f14436
--- /dev/null
+++ b/testing/web-platform/tests/bluetooth/script-tests/service/service-is-removed.js
@@ -0,0 +1,20 @@
+'use strict';
+const test_desc = 'Service is removed before FUNCTION_NAME call. ' +
+ 'Reject with InvalidStateError.';
+const expected = new DOMException('GATT Service no longer exists.',
+ 'InvalidStateError');
+let service, fake_service, fake_peripheral;
+
+bluetooth_test(() => getHealthThermometerService()
+ .then(_ => ({service, fake_service, fake_peripheral} = _))
+ .then(() => fake_service.remove())
+ .then(() => fake_peripheral.simulateGATTServicesChanged())
+ .then(() => assert_promise_rejects_with_message(
+ service.CALLS([
+ getCharacteristic('measurement_interval')|
+ getCharacteristics()|
+ getCharacteristics('measurement_interval')[UUID]
+ ]),
+ expected,
+ 'Service got removed.')),
+ test_desc);