diff --git a/webrtc/test/w3c/getusermedia_conformance_test.html b/webrtc/test/w3c/getusermedia_conformance_test.html index 3b58301ca2..8a95032e26 100644 --- a/webrtc/test/w3c/getusermedia_conformance_test.html +++ b/webrtc/test/w3c/getusermedia_conformance_test.html @@ -1,7 +1,16 @@ + - GetUserMedia Browser Conformance Test + getUserMedia Browser Conformance Test @@ -18,328 +27,37 @@ Notice that this requires the site you're browsing to use HTTPS.

This page contains a foundation of conformance tests that can be expanded to cover most things in the W3C specification of the Media Capture and Streams API.

-

VERSION: These tests are based on the W3C Editor's Draft of 25 September - 2012. +

VERSION: These tests are based on the W3C Editor's Draft of August 24th, + 2013 + (http://dev.w3.org/2011/webrtc/editor/archives/20130824/getusermedia.html)

STATUS: In its current state, it only performs simple checks on the various attributes and methods of the objects exposed by the API. There's not much - functionality tested so far.

-

PREREQUISITES: You must have a webcam available on the machine that the - test is executed at.

-

PREFIX: These tests currently utilizes the

webkit
prefix, so - that will have to changed in order to to test conformance with the actual - standard!

-

SPEC: - http://dev.w3.org/2011/webrtc/editor/getusermedia.html

+ functionality tested so far. The spec doesn't define if an attribute shall be + owned by the object itself (assert_own_propety) or if it shall be + inherited (assert_inherits). Since testharness.js doesn't offer + any generic function that covers both, the method for verification is + currently chosen according to the current Chrome implementation.

+

PREFIXES: These tests currently utilizes the adapter.js + script, which handle the prefixes used by different browsers.

+

HOW TO RUN: The easiest way is to tell your browser to: +

+ Then just load this HTML file to execute the tests.

+ + + + - - + diff --git a/webrtc/test/w3c/getusermedia_conformance_test.js b/webrtc/test/w3c/getusermedia_conformance_test.js new file mode 100644 index 0000000000..6be2932078 --- /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); +});