Update getUserMedia W3C conformance tests.
This CL updates these tests to the spec as of
http://dev.w3.org/2011/webrtc/editor/archives/20130824/getusermedia.html
There are still a lot of functionality that lacks testing. I've put a bunch of TODOs in there but I'm unlikely to get time to implement them all any time soon...
TEST=local testing with Chrome Canary.
BUG=
R=phoglund@webrtc.org
Review URL: https://webrtc-codereview.appspot.com/2639004
git-svn-id: http://webrtc.googlecode.com/svn/trunk@5090 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/webrtc/test/w3c/getusermedia_conformance_test.js b/webrtc/test/w3c/getusermedia_conformance_test.js
new file mode 100644
index 0000000..6be2932
--- /dev/null
+++ b/webrtc/test/w3c/getusermedia_conformance_test.js
@@ -0,0 +1,451 @@
+// Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the LICENSE file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+
+setup({timeout:10000});
+
+// Helper functions to minimize code duplication.
+function failedCallback(test) {
+ return test.step_func(function (error) {
+ assert_unreached('Should not get an error callback');
+ });
+}
+function invokeGetUserMedia(test, okCallback) {
+ getUserMedia({ video: true, audio: true }, okCallback,
+ failedCallback(test));
+}
+
+// 4.2 MediaStream.
+var mediaStreamTest = async_test('4.2 MediaStream');
+
+function verifyMediaStream(stream) {
+ // TODO(kjellander): Add checks for default values where applicable.
+ test(function () {
+ assert_own_property(stream, 'id');
+ assert_true(typeof stream.id === 'string');
+ assert_readonly(stream, 'id');
+ }, '[MediaStream] id attribute');
+
+ test(function () {
+ assert_inherits(stream, 'getAudioTracks');
+ assert_true(typeof stream.getAudioTracks === 'function');
+ }, '[MediaStream] getAudioTracks function');
+
+ test(function () {
+ assert_inherits(stream, 'getVideoTracks');
+ assert_true(typeof stream.getVideoTracks === 'function');
+ }, '[MediaStream] getVideoTracks function');
+
+ test(function () {
+ assert_inherits(stream, 'getTrackById');
+ assert_true(typeof stream.getTrackById === 'function');
+ }, '[MediaStream] getTrackById function');
+
+ test(function () {
+ assert_inherits(stream, 'addTrack');
+ assert_true(typeof stream.addTrack === 'function');
+ }, '[MediaStream] addTrack function');
+
+ test(function () {
+ assert_inherits(stream, 'removeTrack');
+ assert_true(typeof stream.removeTrack === 'function');
+ }, '[MediaStream] removeTrack function');
+
+ test(function () {
+ // Missing in Chrome.
+ assert_inherits(stream, 'clone');
+ assert_true(typeof stream.clone === 'function');
+ }, '[MediaStream] clone function');
+
+ test(function () {
+ assert_own_property(stream, 'ended');
+ assert_true(typeof stream.ended === 'boolean');
+ assert_readonly(stream, 'ended');
+ }, '[MediaStream] ended attribute');
+
+ test(function () {
+ assert_own_property(stream, 'onended');
+ assert_true(stream.onended === null);
+ }, '[MediaStream] onended EventHandler');
+
+ test(function () {
+ assert_own_property(stream, 'onaddtrack');
+ assert_true(stream.onaddtrack === null);
+ }, '[MediaStream] onaddtrack EventHandler');
+
+ test(function () {
+ assert_own_property(stream, 'onremovetrack');
+ assert_true(stream.onremovetrack === null);
+ }, '[MediaStream] onremovetrack EventHandler');
+}
+
+mediaStreamTest.step(function() {
+ var okCallback = mediaStreamTest.step_func(function (stream) {
+ verifyMediaStream(stream);
+
+ var videoTracks = stream.getVideoTracks();
+ assert_true(videoTracks.length > 0);
+
+ // Verify event handlers are working.
+ stream.onaddtrack = onAddTrackCallback
+ stream.onremovetrack = onRemoveTrackCallback
+ stream.removeTrack(videoTracks[0]);
+ stream.addTrack(videoTracks[0]);
+ mediaStreamTest.done();
+ });
+ var onAddTrackCallback = mediaStreamTest.step_func(function () {
+ // TODO(kjellander): verify number of tracks.
+ mediaStreamTest.done();
+ });
+ var onRemoveTrackCallback = mediaStreamTest.step_func(function () {
+ // TODO(kjellander): verify number of tracks.
+ mediaStreamTest.done();
+ });
+ invokeGetUserMedia(mediaStreamTest, okCallback);;
+});
+
+// 4.3 MediaStreamTrack.
+var mediaStreamTrackTest = async_test('4.3 MediaStreamTrack');
+
+function verifyTrack(type, track) {
+ test(function () {
+ assert_own_property(track, 'kind');
+ assert_readonly(track, 'kind');
+ assert_true(typeof track.kind === 'string',
+ 'kind is an object (DOMString)');
+ }, '[MediaStreamTrack (' + type + ')] kind attribute');
+
+ test(function () {
+ assert_own_property(track, 'id');
+ assert_readonly(track, 'id');
+ assert_true(typeof track.id === 'string',
+ 'id is an object (DOMString)');
+ }, '[MediaStreamTrack (' + type + ')] id attribute');
+
+ test(function () {
+ assert_own_property(track, 'label');
+ assert_readonly(track, 'label');
+ assert_true(typeof track.label === 'string',
+ 'label is an object (DOMString)');
+ }, '[MediaStreamTrack (' + type + ')] label attribute');
+
+ test(function () {
+ assert_own_property(track, 'enabled');
+ assert_true(typeof track.enabled === 'boolean');
+ assert_true(track.enabled, 'enabled property must be true initially');
+ }, '[MediaStreamTrack (' + type + ')] enabled attribute');
+
+ test(function () {
+ // Missing in Chrome.
+ assert_own_property(track, 'muted');
+ assert_readonly(track, 'muted');
+ assert_true(typeof track.muted === 'boolean');
+ assert_false(track.muted, 'muted property must be false initially');
+ }, '[MediaStreamTrack (' + type + ')] muted attribute');
+
+ test(function () {
+ assert_own_property(track, 'onmute');
+ assert_true(track.onmute === null);
+ }, '[MediaStreamTrack (' + type + ')] onmute EventHandler');
+
+ test(function () {
+ assert_own_property(track, 'onunmute');
+ assert_true(track.onunmute === null);
+ }, '[MediaStreamTrack (' + type + ')] onunmute EventHandler');
+
+ test(function () {
+ // Missing in Chrome.
+ assert_own_property(track, '_readonly');
+ assert_readonly(track, '_readonly');
+ assert_true(typeof track._readonly === 'boolean');
+ }, '[MediaStreamTrack (' + type + ')] _readonly attribute');
+
+ test(function () {
+ // Missing in Chrome.
+ assert_own_property(track, 'remote');
+ assert_readonly(track, 'remote');
+ assert_true(typeof track.remote === 'boolean');
+ }, '[MediaStreamTrack (' + type + ')] remote attribute');
+
+ test(function () {
+ assert_own_property(track, 'readyState');
+ assert_readonly(track, 'readyState');
+ assert_true(typeof track.readyState === 'string');
+ // TODO(kjellander): verify the initial state.
+ }, '[MediaStreamTrack (' + type + ')] readyState attribute');
+
+ test(function () {
+ // Missing in Chrome.
+ assert_own_property(track, 'onstarted');
+ assert_true(track.onstarted === null);
+ }, '[MediaStreamTrack (' + type + ')] onstarted EventHandler');
+
+ test(function () {
+ assert_own_property(track, 'onended');
+ assert_true(track.onended === null);
+ }, '[MediaStreamTrack (' + type + ')] onended EventHandler');
+
+ test(function () {
+ // Missing in Chrome.
+ assert_inherits(track, 'getSourceInfos');
+ assert_true(typeof track.getSourceInfos === 'function');
+ }, '[MediaStreamTrack (' + type + ')]: getSourceInfos function');
+
+ test(function () {
+ // Missing in Chrome.
+ assert_inherits(track, 'constraints');
+ assert_true(typeof track.constraints === 'function');
+ }, '[MediaStreamTrack (' + type + ')]: constraints function');
+
+ test(function () {
+ // Missing in Chrome.
+ assert_inherits(track, 'states');
+ assert_true(typeof track.states === 'function');
+ }, '[MediaStreamTrack (' + type + ')]: states function');
+
+ test(function () {
+ // Missing in Chrome.
+ assert_inherits(track, 'capabilities');
+ assert_true(typeof track.capabilities === 'function');
+ }, '[MediaStreamTrack (' + type + ')]: capabilities function');
+
+ test(function () {
+ // Missing in Chrome.
+ assert_inherits(track, 'applyConstraints');
+ assert_true(typeof track.applyConstraints === 'function');
+ }, '[MediaStreamTrack (' + type + ')]: applyConstraints function');
+
+ test(function () {
+ // Missing in Chrome.
+ assert_own_property(track, 'onoverconstrained');
+ assert_true(track.onoverconstrained === null);
+ }, '[MediaStreamTrack (' + type + ')] onoverconstrained EventHandler');
+
+ test(function () {
+ // Missing in Chrome.
+ assert_inherits(track, 'clone');
+ assert_true(typeof track.clone === 'function');
+ }, '[MediaStreamTrack (' + type + ')] clone function');
+
+ test(function () {
+ // Missing in Chrome.
+ assert_inherits(track, 'stop');
+ assert_true(typeof track.stop === 'function');
+ }, '[MediaStreamTrack (' + type + ')] stop function');
+};
+mediaStreamTrackTest.step(function() {
+ var okCallback = mediaStreamTrackTest.step_func(function (stream) {
+ verifyTrack('audio', stream.getAudioTracks()[0]);
+ verifyTrack('video', stream.getVideoTracks()[0]);
+ mediaStreamTrackTest.done();
+ });
+ invokeGetUserMedia(mediaStreamTrackTest, okCallback);
+});
+
+mediaStreamTrackTest.step(function() {
+ var okCallback = mediaStreamTrackTest.step_func(function (stream) {
+ // Verify event handlers are working.
+ var track = stream.getVideoTracks()[0];
+ track.onended = onendedCallback
+ track.stop();
+ mediaStreamTrackTest.done();
+ });
+ var onendedCallback = mediaStreamTrackTest.step_func(function () {
+ assert_true(track.ended);
+ mediaStreamTrackTest.done();
+ });
+ invokeGetUserMedia(mediaStreamTrackTest, okCallback);
+});
+
+// 4.4 MediaStreamTrackEvent tests.
+var mediaStreamTrackEventTest = async_test('4.4 MediaStreamTrackEvent');
+mediaStreamTrackEventTest.step(function() {
+ var okCallback = mediaStreamTrackEventTest.step_func(function (stream) {
+ // TODO(kjellander): verify attributes
+ mediaStreamTrackEventTest.done();
+ });
+ invokeGetUserMedia(mediaStreamTrackEventTest, okCallback);
+});
+
+// 4.5 Video and Audio Tracks tests.
+var avTracksTest = async_test('4.5 Video and Audio Tracks');
+avTracksTest.step(function() {
+ var okCallback = avTracksTest.step_func(function (stream) {
+ // TODO(kjellander): verify attributes
+ avTracksTest.done();
+ });
+ invokeGetUserMedia(avTracksTest, okCallback);
+});
+
+// 5. The model: sources, sinks, constraints, and states
+
+// 6. Source states
+// 6.1 Dictionary MediaSourceStates Members
+
+// 7. Source capabilities
+// 7.1 Dictionary CapabilityRange Members
+// 7.2 CapabilityList array
+// 7.3 Dictionary AllVideoCapabilities Members
+// 7.4 Dictionary AllAudioCapabilities Members
+
+// 8. URL tests.
+var createObjectURLTest = async_test('8.1 URL createObjectURL method');
+createObjectURLTest.step(function() {
+ var okCallback = createObjectURLTest.step_func(function (stream) {
+ var url = webkitURL.createObjectURL(stream);
+ assert_true(typeof url === 'string');
+ createObjectURLTest.done();
+ });
+ invokeGetUserMedia(createObjectURLTest, okCallback);
+});
+
+// 9. MediaStreams as Media Elements.
+var mediaElementsTest = async_test('9. MediaStreams as Media Elements');
+
+function verifyVideoTagWithStream(videoTag) {
+ test(function () {
+ assert_equals(videoTag.buffered.length, 0);
+ }, '[Video tag] buffered attribute');
+
+ test(function () {
+ // Attempts to alter currentTime shall be ignored.
+ assert_true(videoTag.currentTime >= 0);
+ assert_throws('InvalidStateError',
+ function () { videoTag.currentTime = 1234; },
+ 'Attempts to modify currentTime shall throw ' +
+ 'InvalidStateError');
+ }, '[Video tag] currentTime attribute');
+
+ test(function () {
+ assert_equals(videoTag.duration, Infinity, 'videoTag.duration');
+ }, '[Video tag] duration attribute');
+
+ test(function () {
+ assert_false(videoTag.seeking, 'videoTag.seeking');
+ }, '[Video tag] seeking attribute');
+
+ test(function () {
+ assert_equals(videoTag.defaultPlaybackRate, 1.0);
+ assert_throws('DOMException',
+ function () { videoTag.defaultPlaybackRate = 2.0; },
+ 'Attempts to alter videoTag.defaultPlaybackRate MUST fail');
+ }, '[Video tag] defaultPlaybackRate attribute');
+
+ test(function () {
+ assert_equals(videoTag.playbackRate, 1.0);
+ assert_throws('DOMException',
+ function () { videoTag.playbackRate = 2.0; },
+ 'Attempts to alter videoTag.playbackRate MUST fail');
+ }, '[Video tag] playbackRate attribute');
+
+ test(function () {
+ assert_equals(videoTag.played.length, 1, 'videoTag.played.length');
+ assert_equals(videoTag.played.start(0), 0);
+ assert_true(videoTag.played.end(0) >= videoTag.currentTime);
+ }, '[Video tag] played attribute');
+
+ test(function () {
+ assert_equals(videoTag.seekable.length, 0);
+ assert_equals(videoTag.seekable.start(), videoTag.currentTime);
+ assert_equals(videoTag.seekable.end(), videoTag.currentTime);
+ assert_equals(videoTag.startDate, NaN, 'videoTag.startDate');
+ }, '[Video tag] seekable attribute');
+
+ test(function () {
+ assert_false(videoTag.loop);
+ }, '[Video tag] loop attribute');
+};
+
+mediaElementsTest.step(function() {
+ var okCallback = mediaElementsTest.step_func(function (stream) {
+ var videoTag = document.getElementById('local-view');
+ // Call the polyfill wrapper to attach the media stream to this element.
+ attachMediaStream(videoTag, stream);
+ verifyVideoTagWithStream(videoTag);
+ mediaElementsTest.done();
+ });
+ invokeGetUserMedia(mediaElementsTest, okCallback);
+});
+
+// 11. Obtaining local multimedia content.
+
+// 11.1 NavigatorUserMedia.
+var getUserMediaTest = async_test('11.1 NavigatorUserMedia');
+getUserMediaTest.step(function() {
+ var okCallback = getUserMediaTest.step_func(function (stream) {
+ assert_true(stream !== null);
+ getUserMediaTest.done();
+ });
+
+ // boolean parameters, without failure callback:
+ getUserMedia({ video: true, audio: true }, okCallback);
+ getUserMedia({ video: true, audio: false }, okCallback);
+ getUserMedia({ video: false, audio: true }, okCallback);
+
+ // boolean parameters, with failure callback:
+ getUserMedia({ video: true, audio: true }, okCallback,
+ failedCallback(getUserMediaTest));
+ getUserMedia({ video: true, audio: false }, okCallback,
+ failedCallback(getUserMediaTest));
+ getUserMedia({ video: false, audio: true }, okCallback,
+ failedCallback(getUserMediaTest));
+});
+
+// 11.2 MediaStreamConstraints.
+var constraintsTest = async_test('11.2 MediaStreamConstraints');
+constraintsTest.step(function() {
+ var okCallback = constraintsTest.step_func(function (stream) {
+ assert_true(stream !== null);
+ constraintsTest.done();
+ });
+
+ // Constraints on video.
+ // See http://webrtc.googlecode.com/svn/trunk/samples/js/demos/html/constraints-and-stats.html
+ // for more examples of constraints.
+ var constraints = {};
+ constraints.audio = true;
+ constraints.video = { mandatory: {}, optional: [] };
+ constraints.video.mandatory.minWidth = 640;
+ constraints.video.mandatory.minHeight = 480;
+ constraints.video.mandatory.minFrameRate = 15;
+
+ getUserMedia(constraints, okCallback, failedCallback(constraintsTest));
+});
+
+// 11.3 NavigatorUserMediaSuccessCallback.
+var successCallbackTest =
+ async_test('11.3 NavigatorUserMediaSuccessCallback');
+successCallbackTest.step(function() {
+ var okCallback = successCallbackTest.step_func(function (stream) {
+ assert_true(stream !== null);
+ successCallbackTest.done();
+ });
+ invokeGetUserMedia(successCallbackTest, okCallback);
+});
+
+// 11.4 NavigatorUserMediaError and NavigatorUserMediaErrorCallback.
+var errorCallbackTest = async_test('11.4 NavigatorUserMediaError and ' +
+ 'NavigatorUserMediaErrorCallback');
+errorCallbackTest.step(function() {
+ var okCallback = errorCallbackTest.step_func(function (stream) {
+ assert_unreached('Should not get a success callback');
+ });
+ var errorCallback = errorCallbackTest.step_func(function (error) {
+ assert_own_property(error, 'name');
+ assert_readonly(error.name);
+ assert_true(typeof error.name === 'string');
+ assert_equals(error.name, 'ConstraintNotSatisfiedError', 'error.name');
+ errorCallbackTest.done();
+ });
+ // Setting both audio and video to false triggers an error callback.
+ // TODO(kjellander): Figure out if there's a way in the spec to trigger an
+ // error callback.
+
+ // TODO(kjellander): Investigate why the error callback is not called when
+ // false/false is provided in Chrome.
+ getUserMedia({ video: false, audio: false }, okCallback, errorCallback);
+});