| <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> |
| <html> |
| <head> |
| <title>GetUserMedia Browser Conformance Test</title> |
| <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> |
| <meta charset="utf-8"> |
| </head> |
| |
| <!-- |
| To quickly iterate when developing this test, make sure you select |
| 'Always allow this site to use this webcam' option in the dropdown menu of |
| Chrome when it's requesting access to your webcam. |
| Notice that this requires the site you're browsing to use HTTPS. |
| --> |
| |
| <body> |
| <h1>Conformance test for the Media Capture and Streams API</h1> |
| <p>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.</p> |
| <p>VERSION: These tests are based on the W3C Editor's Draft of 25 September |
| 2012. |
| <p>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.</p> |
| <p>PREREQUISITES: You must have a webcam available on the machine that the |
| test is executed at.</p> |
| <p>PREFIX: These tests currently utilizes the <pre>webkit</pre> prefix, so |
| that will have to changed in order to to test conformance with the actual |
| standard!</p> |
| <p>SPEC: <a href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html"> |
| http://dev.w3.org/2011/webrtc/editor/getusermedia.html</a></p> |
| |
| <div id="log"></div> |
| <script src="https://w3c-test.org/resources/testharness.js"></script> |
| |
| <script type="text/javascript"> |
| 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) { |
| navigator.webkitGetUserMedia({ video: true, audio: true }, okCallback, |
| failedCallback(test)); |
| } |
| |
| // MediaStream. |
| var mediaStreamTest = async_test('4.2 MediaStream'); |
| function verifyMediaStream(stream) { |
| test(function () { |
| assert_own_property(stream, 'label'); |
| assert_true(typeof stream.label === 'string'); |
| assert_readonly(stream, 'label'); |
| }, "MediaStream label attribute"); |
| |
| test(function () { |
| assert_own_property(stream, 'audioTracks'); |
| assert_true(typeof stream.audioTracks === 'object', |
| 'audioTracks is a MediaStreamTrackList'); |
| assert_readonly(stream, 'audioTracks'); |
| }, "MediaStream audioTracks attribute"); |
| |
| test(function () { |
| assert_own_property(stream, 'videoTracks'); |
| assert_true(typeof stream.videoTracks === 'object', |
| 'videoTracks is a MediaStreamTrackList'); |
| assert_readonly(stream, 'videoTracks'); |
| }, "MediaStream videoTracks attribute"); |
| |
| test(function () { |
| assert_own_property(stream, 'ended'); |
| assert_true(typeof stream.ended === 'boolean'); |
| assert_false(stream.ended); |
| }, "MediaStream ended attribute"); |
| |
| test(function () { |
| assert_own_property(stream, 'onended'); |
| assert_true(stream.onended === null); |
| }, "MediaStream onended EventHandler"); |
| } |
| mediaStreamTest.step(function() { |
| var okCallback = mediaStreamTest.step_func(function (localStream) { |
| verifyMediaStream(localStream); |
| |
| // Verify event handlers are working. |
| localStream.onended = onendedCallback |
| assert_false(localStream.ended); |
| localStream.stop(); |
| }); |
| var onendedCallback = mediaStreamTest.step_func(function () { |
| assert_true(localStream.ended); |
| mediaStreamTest.done(); |
| }); |
| invokeGetUserMedia(mediaStreamTest, okCallback);; |
| }); |
| |
| // LocalMediaStream. |
| var localMediaStreamTest = async_test('4.3 LocalMediaStream'); |
| localMediaStreamTest.step(function() { |
| var okCallback = localMediaStreamTest.step_func(function (localStream) { |
| assert_own_property(localStream, 'stop'); |
| assert_true(typeof localStream.stop === 'function'); |
| localMediaStreamTest.done(); |
| }); |
| invokeGetUserMedia(localMediaStreamTest, okCallback); |
| }); |
| |
| // MediaStreamTrack. |
| var mediaStreamTrackTest = async_test('4.4 MediaStreamTrack'); |
| function verifyTrack(type, track) { |
| test(function () { |
| assert_own_property(track, 'kind'); |
| assert_readonly(track, 'kind'); |
| }, 'MediaStreamTrack (' + type + ') kind attribute'); |
| |
| test(function () { |
| assert_own_property(track, 'label'); |
| assert_true(typeof track.label === 'string', |
| 'label is an object (DOMString)'); |
| assert_readonly(track, 'label'); |
| }, 'MediaStreamTrack (' + type + ') label attribute'); |
| |
| test(function () { |
| assert_own_property(track, 'enabled'); |
| assert_true(typeof track.enabled === 'boolean', |
| 'enabled is an object (DOMString)'); |
| assert_true(track.enabled, 'enabled property must be true initially'); |
| }, 'MediaStreamTrack (' + type + ') enabled attribute'); |
| |
| test(function () { |
| assert_own_property(track, 'readyState'); |
| assert_true(typeof track.readyState === 'number'); |
| assert_readonly(track, 'readyState'); |
| assert_equals(track.readyState, track.LIVE, |
| 'readyState must be LIVE initially'); |
| }, 'MediaStreamTrack (' + type + ') readyState 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 () { |
| assert_own_property(track, 'onended'); |
| assert_true(track.onended === null); |
| }, 'MediaStreamTrack (' + type + ') onended EventHandler'); |
| } |
| mediaStreamTrackTest.step(function() { |
| var okCallback = mediaStreamTrackTest.step_func(function (localStream) { |
| verifyTrack('audio', localStream.audioTracks[0]); |
| verifyTrack('video', localStream.videoTracks[0]); |
| mediaStreamTrackTest.done(); |
| }); |
| invokeGetUserMedia(mediaStreamTrackTest, okCallback); |
| }); |
| |
| // URL tests. |
| var createObjectURLTest = async_test('4.5 URL createObjectURL method'); |
| createObjectURLTest.step(function() { |
| var okCallback = createObjectURLTest.step_func(function (localStream) { |
| var url = webkitURL.createObjectURL(localStream); |
| assert_true(typeof url === 'string'); |
| createObjectURLTest.done(); |
| }); |
| invokeGetUserMedia(createObjectURLTest, okCallback); |
| }); |
| |
| // MediaStreamTrackList tests. |
| var mediaStreamTrackListTest = async_test('4.6 MediaStreamTrackList'); |
| function verifyTrackList(type, trackList) { |
| test(function () { |
| assert_own_property(trackList, 'length'); |
| assert_true(typeof trackList.length === 'number'); |
| assert_true(trackList.length === 1); |
| }, 'MediaStreamTrackList (' + type + ') length attribute'); |
| |
| test(function () { |
| assert_own_property(trackList, 'item'); |
| assert_true(typeof trackList.item === 'function'); |
| assert_true(trackList.item(0) !== null); |
| }, 'MediaStreamTrackList (' + type + ') item() method'); |
| |
| test(function () { |
| assert_own_property(trackList, 'add'); |
| assert_true(typeof trackList.add === 'function'); |
| }, 'MediaStreamTrackList (' + type + ') add() method'); |
| |
| test(function () { |
| assert_own_property(trackList, 'remove'); |
| assert_true(typeof trackList.remove === 'function'); |
| }, 'MediaStreamTrackList (' + type + ') remove() method'); |
| |
| test(function () { |
| assert_own_property(trackList, 'onaddtrack'); |
| assert_true(trackList.onaddtrack === null); |
| }, 'MediaStreamTrackList (' + type + ') onaddtrack EventHandler'); |
| |
| test(function () { |
| assert_own_property(trackList, 'onremovetrack'); |
| assert_true(trackList.onremovetrack === null); |
| }, 'MediaStreamTrackList (' + type + ') onremovetrack EventHandler'); |
| } |
| mediaStreamTrackListTest.step(function() { |
| var okCallback = mediaStreamTrackListTest.step_func(function (localStream) { |
| verifyTrackList('audioTracks', localStream.audioTracks); |
| verifyTrackList('videoTracks', localStream.videoTracks); |
| mediaStreamTrackListTest.done(); |
| }); |
| invokeGetUserMedia(mediaStreamTrackListTest, okCallback); |
| }); |
| |
| // MediaStreams as Media Elements. |
| var mediaElementsTest = async_test('4.7 MediaStreams as Media Elements'); |
| mediaElementsTest.step(function() { |
| var okCallback = mediaElementsTest.step_func(function (localStream) { |
| var url = webkitURL.createObjectURL(localStream); |
| var videoTag = document.getElementById('local-view'); |
| videoTag.src = url; |
| assert_equals(videoTag.currentSrc, url); |
| assert_false(videoTag.preload); |
| assert_equals(videoTag.buffered.length, 1); |
| assert_equals(videoTag.buffered.start(0), 0); |
| assert_equals(videoTag.buffered.end(0), 0); |
| // Attempts to alter currentTime shall be ignored. |
| assert_true(videoTag.currentTime >= 0); |
| var time = videoTag.currentTime; |
| videoTag.currentTime = time - 100; |
| assert_true(videoTag.currentTime >= time); |
| |
| assert_equals(videoTag.duration, infinity); |
| assert_false(videoTag.seeking); |
| assert_equals(videoTag.defaultPlaybackRate, 1.0); |
| // Attempts to alter defaultPlaybackRate shall fail. |
| assert_throws(videoTag.defaultPlaybackRate = 2.0); |
| |
| assert_equals(videoTag.playbackRate, 1.0); |
| // Attempts to alter playbackRate shall fail. |
| assert_throws(videoTag.playbackRate = 2.0); |
| |
| assert_equals(videoTag.played.length, 1); |
| assert_equals(videoTag.played.start(0), 0); |
| assert_true(videoTag.played.end(0) >= videoTag.currentTime); |
| assert_equals(videoTag.seekable.length, 0); |
| assert_equals(videoTag.seekable.start(), videoTag.currentTime); |
| assert_equals(videoTag.seekable.end(), videoTag.currentTime); |
| assert_equals(videoTag.startOffsetTime, NaN); |
| assert_false(videoTag.loop); |
| mediaElementsTest.done(); |
| }); |
| invokeGetUserMedia(mediaElementsTest, okCallback); |
| }); |
| |
| // NavigatorUserMedia. |
| var getUserMediaTest = async_test('5.1 NavigatorUserMedia'); |
| getUserMediaTest.step(function() { |
| var okCallback = getUserMediaTest.step_func(function (localStream) { |
| assert_true(localStream !== null); |
| getUserMediaTest.done(); |
| }); |
| |
| // boolean parameters, without failure callback: |
| navigator.webkitGetUserMedia({ video: true, audio: true }, okCallback); |
| navigator.webkitGetUserMedia({ video: true, audio: false }, okCallback); |
| navigator.webkitGetUserMedia({ video: false, audio: true }, okCallback); |
| |
| // boolean parameters, with failure callback: |
| navigator.webkitGetUserMedia({ video: true, audio: true }, okCallback, |
| failedCallback(getUserMediaTest)); |
| navigator.webkitGetUserMedia({ video: true, audio: false }, okCallback, |
| failedCallback(getUserMediaTest)); |
| navigator.webkitGetUserMedia({ video: false, audio: true }, okCallback, |
| failedCallback(getUserMediaTest)); |
| }); |
| |
| // MediaStreamConstraints. |
| var constraintsTest = |
| async_test('5.2 MediaStreamConstraints'); |
| constraintsTest.step(function() { |
| var okCallback = constraintsTest.step_func(function (localStream) { |
| assert_true(localStream !== null); |
| constraintsTest.done(); |
| }); |
| |
| // Constraints on video. |
| // See http://code.google.com/p/webrtc-samples/source/browse/trunk/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; |
| |
| navigator.webkitGetUserMedia(constraints, okCallback, |
| failedCallback(constraintsTest)); |
| }); |
| |
| // NavigatorUserMediaSuccessCallback. |
| var successCallbackTest = |
| async_test('5.3 NavigatorUserMediaSuccessCallback'); |
| successCallbackTest.step(function() { |
| var okCallback = successCallbackTest.step_func(function (localStream) { |
| assert_true(localStream !== null); |
| assert_own_property(localStream, 'stop'); |
| successCallbackTest.done(); |
| }); |
| invokeGetUserMedia(successCallbackTest, okCallback); |
| }); |
| |
| // NavigatorUserMediaError and NavigatorUserMediaErrorCallback. |
| var errorCallbackTest = |
| async_test('5.4 NavigatorUserMediaError and ' + |
| 'NavigatorUserMediaErrorCallback'); |
| errorCallbackTest.step(function() { |
| var okCallback = errorCallbackTest.step_func(function (localStream) { |
| assert_unreached('Should not get a success callback'); |
| }); |
| var errorCallback = errorCallbackTest.step_func(function (error) { |
| assert_own_property(error, 'PERMISSION_DENIED'); |
| assert_readonly(error.PERMISSION_DENIED); |
| assert_true(typeof error.PERMISSION_DENIED === 'number'); |
| assert_own_property(error, 'code'); |
| assert_readonly(error.code); |
| assert_true(typeof error.code === 'number'); |
| |
| }); |
| // 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. |
| navigator.webkitGetUserMedia({ video: false, audio: false }, okCallback, |
| errorCallback); |
| }); |
| </script> |
| <video width="640" height="480" id="local-view" autoplay="autoplay"></video> |
| </body> |
| </html> |