diff --git a/.travis.yml b/.travis.yml index 1627dc1..5cc1cdf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,4 @@ env: global: - COMMIT_AUTHOR_NAME="Travis CI" - COMMIT_AUTHOR_EMAIL="travis-ci@w3.org" - - SPECS="index" + - SPECS="index test/index" diff --git a/README.md b/README.md index 5bc3480..3f81b43 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Documents * [Explainer](explainer.md) * [Specification](https://wicg.github.io/webusb/) (Editor's Draft) +* [Test API Specification](https://wicg.github.io/webusb/test/) (Editor's Draft) Communication ------------- diff --git a/test/index.bs b/test/index.bs new file mode 100644 index 0000000..535fab7 --- /dev/null +++ b/test/index.bs @@ -0,0 +1,265 @@ +
+Title: WebUSB Testing API +Status: ED +ED: https://wicg.github.io/webusb/test.html +Shortname: webusb-test +Level: 1 +Editor: Reilly Grant, Google Inc., reillyg@google.com +Abstract: This document describes an API for testing a User Agent's implementation of the WebUSB API. +Group: wicg +Repository: https://github.com/WICG/webusb/ +!Participate: Join the W3C Community Group +!Participate: IRC: #webusb on W3C's IRC (Stay around for an answer, it make take a while) ++ +# Introduction # {#intro} + +This section is non-normative. + +Standards such as [[WebUSB]] pose a challenge to test authors because to fully +exercise their interfaces requires physical hardware devices that respond in +predictable ways. To address this challenge this specification defines an +interface for controlling a simulation of the USB subsystem on the host the +User Agent is running on. With this interface devices with particular properties +can be created and their responses to requests are well defined. + +The purpose if this interface is to assist the developers of UAs and so this +interface does not permit arbitrary control over the behavior of devices. Some +parameters are configurable and some are specified. As such it may not be +sufficient for developers wishing to simulate the behavior of a particular +device in order to test applications built on top of the UA. + +## Examples ## {#examples} + +
+ let fakeDeviceInit = { + usbVersionMajor: 2, + usbVersionMinor: 0, + usbVersionSubminor: 0, + deviceClass: 0xFF, + deviceSubclass: 0xFF, + deviceProtocol: 0xFF, + vendorId: 0x1234, + productId: 0xABCD, + deviceVersionMajor: 1, + deviceVersionMinor: 0, + deviceVersionSubminor: 0, + configurations: [] + }; + + function promiseForEvent(eventTarget, eventType) { + return new Promise(resolve => { + let eventHandler = evt => { + resolve(evt); + eventTarget.removeEventListener(eventTarget, eventHandler); + }; + eventTarget.addEventListener(eventType); + }); + } + + promise_test(async () => { + await navigator.usb.test.initialize(); + + let fakeDevice = navigator.usb.addFakeDevice(fakeDeviceInit); + let connectEvent = await promiseForEvent(navigator.usb, 'connect'); + let device = connectEvent.device; + assert_equals(device.usbVersionMajor, fakeDeviceInit.usbVersionMajor); + assert_equals(device.usbVersionMinor, fakeDeviceInit.usbVersionMinor); + assert_equals(device.usbVersionSubminor, fakeDeviceInit.usbVersionSubminor); + assert_equals(device.deviceClass, fakeDeviceInit.deviceClass); + assert_equals(device.deviceSubclass, fakeDeviceInit.deviceSubclass); + assert_equals(device.deviceProtocol, fakeDeviceInit.deviceProtocol); + assert_equals(device.vendorId, fakeDeviceInit.vendorId); + assert_equals(device.productId, fakeDeviceInit.productId); + assert_equals(device.deviceVersionMajor, fakeDeviceInit.deviceVersionMajor); + assert_equals(device.deviceVersionMinor, fakeDeviceInit.deviceVersionMinor); + assert_equals(device.deviceVersionSubminor, fakeDeviceInit.deviceVersionSubminor); + assert_equals(device.configuration, null); + assert_equals(device.configurations.length, 0); + + let devices = await navigator.usb.getDevices(); + assert_array_equals(devices, [device]); + + fakeDevice.disconnect(); + let disconnectEvent = await promiseForEvent(navigator.usb, 'disconnect'); + assert_equals(disconnectEvent.device, device); + }); ++
+ partial interface USB { + [SameObject] readonly attribute USBTest test; + }; + + interface USBTest { + attribute EventHandler ondeviceclose; + attribute FakeUSBDevice? chosenDevice; + attribute FrozenArray<USBDeviceFilter>? lastFilters; + + Promise<void> initialize(); + Promise<void> attachToWindow(Window window); + FakeUSBDevice addFakeDevice(FakeUSBDeviceInit deviceInit); + void reset(); + }; + + interface FakeUSBDevice { + void disconnect(); + }; ++ +Instances of {{USBTest}} are created with an internal slot +\[[initializationPromise]] with an initial +value of
null
.
+
+When invoked, the {{USBTest/initialize()}} method MUST run these steps:
+
+1. Let |test| be the {{USBTest}} instance on which this method was invoked.
+1. If |test|.{{USBTest/[[initializationPromise]]}} is null
then
+ set |test|.{{USBTest/[[initializationPromise]]}} to a new {{Promise}} and
+ run these sub-steps in parallel:
+ 1. Reconfigure the UA's internal implementation of the {{Navigator/usb}}
+ object in the current global object so that it is controlled by
+ |test|.
+ 1. Resolve |test|.{{USBTest/[[initializationPromise]]}}.
+1. Return |test|.{{USBTest/[[initializationPromise]]}}.
+
+The behavior of any other method on the {{Navigator/usb}} or {{USB/test}}
+objects in the current global object is undefined by this specification
+if invoked before the {{Promise}} returned by {{USBTest/initialize()}} is
+resolved.
+
+When invoked, the {{USBTest/attachToWindow(window)}} method MUST return a new
+{{Promise}} |promise| and run these steps in parallel:
+
+1. Let |test| by the {{USBTest}} instance on which this method was invoked.
+1. Reconfigure the UA's internal implementation of {{Navigator/usb}} in the
+ global object window so that it is controlled by
+ |test|.
+1. Resolve |promise|.
+
+When invoked, the {{USBTest/addFakeDevice(deviceInit)}} method MUST return a
+new {{FakeUSBDevice}} and queue a task to, for each {{USB}} instance
+controlled by the target of this invokation, perform the steps described in
+[[!WebUSB]] for handling a new device that is connected to the system.
+
+The {{USBTest/reset()}} method is not currently asynchronous and should probably
+be removed.
+
+When invoked, the {{FakeUSBDevice/disconnect()}} method MUST,
+queue a task to, for each {{USB}} instance controlled by the target of
+this invokation, perform the steps described in [[!WebUSB]] for handling the
+removal of a device that was connected to the system.
+
+# Fake USB Devices # {#fake-devices}
+
+To permit testing without physical hardware this specification defines a method
+for tests to add simulated USB devices by calling {{USBTest/addFakeDevice()}}
+with an instance of {{FakeUSBDeviceInit}} containing the properties of the
+device to be added.
+
++ dictionary FakeUSBDeviceInit { + required octet usbVersionMajor; + required octet usbVersionMinor; + required octet usbVersionSubminor; + required octet deviceClass; + required octet deviceSubclass; + required octet deviceProtocol; + required unsigned short vendorId; + required unsigned short productId; + required octet deviceVersionMajor; + required octet deviceVersionMinor; + required octet deviceVersionSubminor; + DOMString? manufacturerName; + DOMString? productName; + DOMString? serialNumber; + octet activeConfigurationValue = 0; + sequence<FakeUSBConfigurationInit> configurations; + }; + + dictionary FakeUSBConfigurationInit { + required octet configurationValue; + DOMString? configurationName; + sequence<FakeUSBInterfaceInit> interfaces; + }; + + dictionary FakeUSBInterfaceInit { + required octet interfaceNumber; + sequence<FakeUSBAlternateInterfaceInit> alternates; + }; + + dictionary FakeUSBAlternateInterfaceInit { + required octet alternateSetting; + required octet interfaceClass; + required octet interfaceSubclass; + required octet interfaceProtocol; + DOMString? interfaceName; + sequence<FakeUSBEndpointInit> endpoints; + }; + + dictionary FakeUSBEndpointInit { + required octet endpointNumber; + required USBDirection direction; + required USBEndpointType type; + required unsigned long packetSize; + }; ++ +When a fake USB device is initialized from an instance of {{FakeUSBDeviceInit}} +|init| the attributes of the {{USBDevice}} |device| SHALL be initialized as +follows: + +1. For each non-sequence attribute of |init| other than + {{FakeUSBDeviceInit/activeConfigurationValue}} the attribute of |device| + with the same name SHALL be set to its value. +1. For each sequence of {{FakeUSBConfigurationInit}}, {{FakeUSBInterfaceInit}} + {{FakeUSBAlternateInterfaceInit}} and {{FakeUSBEndpointInit}} objects + corresponding {{USBConfiguration}}, {{USBInterface}}, + {{USBAlternateInterface}} and {{USBEndpoint}} objects SHALL be created by + similarly copying attributes with the same names and be used to build an + identical hierarchy of objects in the {{USBDevice/configurations}}, + {{USBConfiguration/interfaces}}, {{USBInterface/alternates}} and + {{USBAlternateInterface/endpoints}} {{FrozenArray}}s. +1. If a {{USBConfiguration}} instance |config| with + {{USBConfiguration/configurationValue}} equal to + |init|.{{FakeUSBDeviceInit/activeConfigurationValue}} then + |device|.{{USBDevice/configuration}} SHALL be set to |config|, otherwise +
null
.
+
++spec: ECMAScript; urlPrefix: https://tc39.github.io/ecma262/# + type: dfn + text: internal slot; url: sec-object-internal-methods-and-internal-slots ++ +
+spec:html; type:dfn; for:/; text:global object +