diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /testing/web-platform/tests/mediacapture-image | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'testing/web-platform/tests/mediacapture-image')
26 files changed, 1775 insertions, 0 deletions
diff --git a/testing/web-platform/tests/mediacapture-image/ImageCapture-MediaTrackSupportedConstraints.https.html b/testing/web-platform/tests/mediacapture-image/ImageCapture-MediaTrackSupportedConstraints.https.html new file mode 100644 index 0000000000..fddeb60d0c --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/ImageCapture-MediaTrackSupportedConstraints.https.html @@ -0,0 +1,29 @@ +<!DOCTYPE HTML> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +// Tests that getSupportedConstraints() returns what it should. +test(function() { + supported_constraints = navigator.mediaDevices.getSupportedConstraints(); + + assert_true(supported_constraints.whiteBalanceMode); + assert_true(supported_constraints.exposureMode); + assert_true(supported_constraints.focusMode); + assert_true(supported_constraints.pointsOfInterest); + assert_true(supported_constraints.exposureCompensation); + assert_true(supported_constraints.exposureTime); + assert_true(supported_constraints.colorTemperature); + assert_true(supported_constraints.iso); + assert_true(supported_constraints.brightness); + assert_true(supported_constraints.contrast); + assert_true(supported_constraints.saturation); + assert_true(supported_constraints.sharpness); + assert_true(supported_constraints.focusDistance); + assert_true(supported_constraints.pan); + assert_true(supported_constraints.tilt); + assert_true(supported_constraints.zoom); + assert_true(supported_constraints.torch); +}, 'Image Capture supported constraints'); + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/ImageCapture-creation.https.html b/testing/web-platform/tests/mediacapture-image/ImageCapture-creation.https.html new file mode 100644 index 0000000000..81a503893a --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/ImageCapture-creation.https.html @@ -0,0 +1,100 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src=/resources/testdriver.js></script> +<script src=/resources/testdriver-vendor.js></script> +<script src='../mediacapture-streams/permission-helper.js'></script> +<script> + +// This test verifies that ImageCapture can be created (or not) with different +// Media Stream Track types (audio, video). + +function makeAsyncTest(modifyTrack, message) { + async_test(function(test) { + + const gotStream = test.step_func(function(stream) { + assert_equals(stream.getAudioTracks().length, 0); + assert_equals(stream.getVideoTracks().length, 1); + + var videoTrack = stream.getVideoTracks()[0]; + assert_equals(videoTrack.readyState, 'live'); + assert_true(videoTrack.enabled); + assert_false(videoTrack.muted); + + var capturer = new ImageCapture(videoTrack); + assert_equals(capturer.track, videoTrack); + + modifyTrack(videoTrack); + + promise_rejects_dom(test, + 'InvalidStateError', + capturer.grabFrame(), + 'Should throw InvalidStateError.') + .then(() => test.done()); + }); + + const onError = test.unreached_func('Error creating MediaStream.'); + // both permissions are needed at some point, asking for both at once + setMediaPermission() + .then(() => navigator.mediaDevices.getUserMedia({video: true})) + .then(gotStream) + .catch(onError); + + }, message); +} + +var disableTrack = function(videoTrack) { + // grabFrame() is rejected if the associated video track is disabled. + videoTrack.enabled = false; +}; + +var stopTrack = function(videoTrack) { + // grabFrame() is rejected if the associated video track is ended. + videoTrack.stop(); + assert_equals(videoTrack.readyState, 'ended'); +}; + +// Create the rejection tests. Note that grabFrame() would also be rejected if +// the video Track was muted but that's a read-only property of the Track. +makeAsyncTest(disableTrack, 'grabFrame() of a disabled Track'); +makeAsyncTest(stopTrack, 'grabFrame() of an ended Track'); + + +var testAudio = async_test(function() { + navigator.mediaDevices.getUserMedia({audio:true}) + .then( + this.step_func(function(stream) { + assert_equals(stream.getAudioTracks().length, 1); + assert_equals(stream.getVideoTracks().length, 0); + assert_throws_dom("NotSupportedError", + function() { + var capturer = new ImageCapture(stream.getAudioTracks()[0]); + }, + 'an ImageCapturer can only be created from a video track'); + + this.done(); + })) + .catch( + this.unreached_func('Error creating MediaStream.')); +}, 'verifies that an ImageCapture cannot be created out of an Audio Track'); + +var testParameter = test(function() { + const invalidParameters = [ + "invalid", + null, + 123, + {}, + "", + true + ]; + assert_throws_js(TypeError, + function() { var capturer = new ImageCapture(); }, + 'an ImageCapturer cannot be created with no parameter'); + invalidParameters.map(parameter => { + assert_throws_js(TypeError, + function() { var capturer = new ImageCapture(parameter); }, + `an ImageCapturer cannot be created with a ${parameter} parameter`); + }); +}, 'throw "TypeError" if parameter is not MediaStreamTrack.'); + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/ImageCapture-grabFrame-crash.html b/testing/web-platform/tests/mediacapture-image/ImageCapture-grabFrame-crash.html new file mode 100644 index 0000000000..d4bf57f0fa --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/ImageCapture-grabFrame-crash.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>Repro of https://crbug.com/1374294</title> +<body></body> +<script> + let iframe = document.createElement('iframe'); + document.body.appendChild(iframe); + let generator = new iframe.contentWindow.MediaStreamTrackGenerator({kind: 'video'}); + const capture = new ImageCapture(generator); + capture.grabFrame(); +</script> diff --git a/testing/web-platform/tests/mediacapture-image/ImageCapture-grabFrame.html b/testing/web-platform/tests/mediacapture-image/ImageCapture-grabFrame.html new file mode 100644 index 0000000000..bf5e9400a0 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/ImageCapture-grabFrame.html @@ -0,0 +1,46 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<canvas id='canvas0' width=10 height=10/> +<canvas id='canvas1' width=10 height=10/> +</body> +<script> + +// This test verifies that ImageCapture can grabFrame()s. + +var test = async_test(function() { + var canvas0 = document.getElementById('canvas0'); + var context0 = canvas0.getContext("2d"); + context0.fillStyle = "red"; + context0.fillRect(0, 0, 10, 10); + + var stream = canvas0.captureStream(); + + var capturer = new ImageCapture(stream.getVideoTracks()[0]); + + capturer.grabFrame() + .then(bitmap => { + assert_equals(canvas0.width, bitmap.width); + assert_equals(canvas0.height, bitmap.height); + + var context1 = document.getElementById('canvas1').getContext("2d"); + context1.drawImage(bitmap, 0, 0); + + var imageData0 = context0.getImageData(0, 0, 10, 10); + var imageData1 = context1.getImageData(0, 0, 10, 10); + + assert_equals(imageData0.width, imageData1.width); + assert_equals(imageData0.height, imageData1.height); + assert_equals(imageData0.data.length, imageData1.data.length); + for (var i = 0; i < imageData0.data.length; i++) + assert_approx_equals(imageData0.data[i], imageData1.data[i], 5); + + this.done(); + }) + .catch(error => { + assert_unreached('Error during grabFrame(): '+ error); + }); +}, 'exercises the ImageCapture API creation and grabFrame().'); + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/ImageCapture-track.html b/testing/web-platform/tests/mediacapture-image/ImageCapture-track.html new file mode 100644 index 0000000000..774970ad0c --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/ImageCapture-track.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>ImageCapture track</title> +<link rel="author" title="Intel" href="http://www.intel.com"> +<link rel="help" href="https://w3c.github.io/mediacapture-image/#dom-imagecapture-track"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<canvas id='canvas' width=10 height=10></canvas> +<script> + +test(t => { + let canvas = document.getElementById('canvas'); + let context = canvas.getContext('2d'); + context.fillStyle = 'red'; + context.fillRect(0, 0, 10, 10); + + let stream = canvas.captureStream(); + let videoTrack = stream.getVideoTracks()[0]; + + let capturer = new ImageCapture(videoTrack); + assert_true(capturer.track instanceof MediaStreamTrack); + assert_equals(capturer.track, videoTrack); + + let cloneTrack = videoTrack.clone(); + assert_not_equals(videoTrack, cloneTrack); + + capturer.track = cloneTrack; + assert_equals(capturer.track, videoTrack); +}, "ImageCapture track attribute is readonly") + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/META.yml b/testing/web-platform/tests/mediacapture-image/META.yml new file mode 100644 index 0000000000..1b4fef5e5a --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/META.yml @@ -0,0 +1,4 @@ +spec: https://w3c.github.io/mediacapture-image/ +suggested_reviewers: + - yellowdoge + - reillyeon diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-fast.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-fast.html new file mode 100644 index 0000000000..d80ab67321 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-fast.html @@ -0,0 +1,67 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<canvas id='canvas' width=10 height=10/> +</body> +<script> + +// This test verifies that MediaStreamTrack.applyConstraints() exists and that, +// when called with no parameters, returns a Promise that is resolved. This +// might not make sense: https://github.com/w3c/mediacapture-main/issues/438 . +// Other tests go deeper. +promise_test(function(t) { + var canvas = document.getElementById('canvas'); + var context = canvas.getContext("2d"); + context.fillStyle = "red"; + context.fillRect(0, 0, 10, 10); + + var stream = canvas.captureStream(); + assert_equals(stream.getAudioTracks().length, 0); + assert_equals(stream.getVideoTracks().length, 1); + + var videoTrack = stream.getVideoTracks()[0]; + return videoTrack.applyConstraints(); +}, 'MediaStreamTrack.applyConstraints()'); + +// This test verifies that MediaStreamTrack.applyConstraints() exists and that, +// when called with an empty advanced constraint set, returns a Promise that is +// resolved. +promise_test(function(t) { +var canvas = document.getElementById('canvas'); +var context = canvas.getContext("2d"); +context.fillStyle = "red"; +context.fillRect(0, 0, 10, 10); + +var stream = canvas.captureStream(); +assert_equals(stream.getAudioTracks().length, 0); +assert_equals(stream.getVideoTracks().length, 1); + +var videoTrack = stream.getVideoTracks()[0]; +return videoTrack.applyConstraints({advanced: []}); +}, 'MediaStreamTrack.applyConstraints({advanced: []})'); + +// This test verifies that applyConstraints() rejects the returned Promise if +// passed a non-supported image-capture constraint (https://crbug.com/711694). +promise_test(async function(t) { + var canvas = document.getElementById('canvas'); + var context = canvas.getContext("2d"); + context.fillStyle = "red"; + context.fillRect(0, 0, 10, 10); + + var stream = canvas.captureStream(); + var videoTrack = stream.getVideoTracks()[0]; + + // Use e.g. |torch| as an example of unsupported constraint. + assert_false("torch" in videoTrack.getCapabilities()); + try { + await videoTrack.applyConstraints({advanced : [ {torch : true} ]}); + } catch (error) { + assert_equals(error.name, 'OverconstrainedError'); + assert_equals(error.constraint, 'torch'); + return; + } + assert_unreached('expected applyConstraints to reject'); +}, 'MediaStreamTrack.applyConstraints() with unsupported constraint'); + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-getSettings.https.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-getSettings.https.html new file mode 100644 index 0000000000..c6401570b8 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-getSettings.https.html @@ -0,0 +1,120 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script> +<script> + +// This test verifies that the |constraints| configured in the mock Mojo +// service implementation, are returned by MediaStreamTrack.getSettings(). + +image_capture_test(async t => { + await test_driver.set_permission({name: 'camera', panTiltZoom: true}, + 'granted'); + + const constraints = { advanced : [{ whiteBalanceMode : 'single-shot', + exposureMode : 'manual', + focusMode : 'single-shot', + + pointsOfInterest : [{x : 0.1, y : 0.2}, + {x : 0.3, y : 0.4}], + + exposureCompensation : 133.77, + // in nano-seconds. + exposureTime : 10000, + colorTemperature : 6000, + iso : 120.0, + + brightness : 3, + contrast : 4, + saturation : 5, + sharpness : 6, + focusDistance : 7, + + pan : 8, + tilt : 9, + zoom : 3.141592, + + torch : true + }]}; + + let stream = await navigator.mediaDevices.getUserMedia({video: true}); + let videoTrack = stream.getVideoTracks()[0]; + + try { + await videoTrack.applyConstraints(constraints); + } catch (error) { + assert_unreached('Error applying constraints: ' + error.message); + } + + let settings = videoTrack.getSettings(); + assert_equals(typeof settings, 'object'); + + assert_equals(constraints.advanced[0].whiteBalanceMode, + settings.whiteBalanceMode, 'whiteBalanceMode'); + assert_equals(constraints.advanced[0].exposureMode, settings.exposureMode, + 'exposureMode'); + assert_equals(constraints.advanced[0].focusMode, settings.focusMode, + 'focusMode'); + + assert_point2d_array_approx_equals( + constraints.advanced[0].pointsOfInterest, settings.pointsOfInterest, + 0.01); + + assert_equals(constraints.advanced[0].exposureCompensation, + settings.exposureCompensation, 'exposureCompensation'); + assert_equals(constraints.advanced[0].exposureTime, + settings.exposureTime, 'exposureTime'); + assert_equals(constraints.advanced[0].colorTemperature, + settings.colorTemperature, 'colorTemperature'); + assert_equals(constraints.advanced[0].iso, settings.iso, 'iso'); + + assert_equals(constraints.advanced[0].brightness, settings.brightness, + 'brightness'); + assert_equals(constraints.advanced[0].contrast, settings.contrast, + 'contrast'); + assert_equals(constraints.advanced[0].saturation, settings.saturation, + 'saturation'); + assert_equals(constraints.advanced[0].sharpness, settings.sharpness, + 'sharpness'); + + assert_equals(constraints.advanced[0].focusDistance, settings.focusDistance, + 'focusDistance'); + + assert_equals(constraints.advanced[0].pan, settings.pan, 'pan'); + assert_equals(constraints.advanced[0].tilt, settings.tilt, 'tilt'); + assert_equals(constraints.advanced[0].zoom, settings.zoom, 'zoom'); + + assert_equals(constraints.advanced[0].torch, settings.torch, 'torch'); + +}, 'exercises an applyConstraints() - getSettings() cycle with PTZ permission granted'); + + +// This test verifies that the PTZ |constraints| configured in the mock Mojo +// service implementation can't be applied if PTZ permission is denied. + +image_capture_test(async t => { + await test_driver.set_permission({name: 'camera', panTiltZoom: true}, + 'denied'); + + let stream = await navigator.mediaDevices.getUserMedia({video: true}); + let videoTrack = stream.getVideoTracks()[0]; + + const constraints = [{ pan: 8 }, { tilt: 9 }]; + await Promise.all(constraints.map(async constraint => { + try { + await videoTrack.applyConstraints({ advanced: [constraint] }); + } catch (error) { + assert_equals(error.name, 'OverconstrainedError'); + assert_equals(error.constraint, Object.keys(constraint)[0]); + return; + } + assert_unreached( + "applyConstraints should throw a NotSupportedError for " + + JSON.stringify(constraint)); + })); + +}, 'exercises an applyConstraints() with PTZ permission denied'); + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-reject.https.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-reject.https.html new file mode 100644 index 0000000000..5050ed27dd --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints-reject.https.html @@ -0,0 +1,77 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script> +<script> + +// This test verifies that MediaStreamTrack.applyConstraints() rejects if any +// passed constraint is unsupported or outside its allowed range. +var makePromiseTest = function(getConstraint) { + image_capture_test(async (t, imageCaptureTest) => { + await test_driver.set_permission({name: 'camera', panTiltZoom: true}, + 'granted'); + + imageCaptureTest.mockImageCapture().state().supportsTorch = false; + + let stream = await navigator.mediaDevices.getUserMedia({video: true}); + let videoTrack = stream.getVideoTracks()[0]; + + const constraint = getConstraint(imageCaptureTest.mockImageCapture().state()); + try { + const constraints = { advanced : [constraint] }; + await videoTrack.applyConstraints(constraints); + assert_unreached('expected applyConstraints to reject'); + } catch (error) { + assert_equals(error.name, 'OverconstrainedError'); + assert_equals(error.constraint, Object.keys(constraint)[0]); + } + }); +}; + +const constraintGenerators = [ + capabilities => ({ whiteBalanceMode: 'manual' }), + capabilities => ({ exposureMode: 'none' }), + capabilities => ({ focusMode: 'continuous' }), + capabilities => ({ + exposureCompensation: capabilities.exposureCompensation.max + 1 + }), + capabilities => ({ + exposureCompensation: capabilities.exposureCompensation.min - 1 + }), + capabilities => ({ + colorTemperature: capabilities.colorTemperature.max + 1 + }), + capabilities => ({ + colorTemperature: capabilities.colorTemperature.min - 1 + }), + capabilities => ({ iso: capabilities.iso.max + 1 }), + capabilities => ({ iso: capabilities.iso.min - 1 }), + capabilities => ({ brightness: capabilities.brightness.max + 1 }), + capabilities => ({ brightness: capabilities.brightness.min - 1 }), + capabilities => ({ contrast: capabilities.contrast.max + 1 }), + capabilities => ({ contrast: capabilities.contrast.min - 1 }), + capabilities => ({ saturation: capabilities.saturation.max + 1 }), + capabilities => ({ saturation: capabilities.saturation.min - 1 }), + capabilities => ({ sharpness: capabilities.sharpness.max + 1 }), + capabilities => ({ sharpness: capabilities.sharpness.min - 1 }), + capabilities => ({ pan: capabilities.pan.max + 1 }), + capabilities => ({ pan: capabilities.pan.min - 1 }), + capabilities => ({ tilt: capabilities.tilt.max + 1 }), + capabilities => ({ tilt: capabilities.tilt.min - 1 }), + capabilities => ({ zoom: capabilities.zoom.max + 1 }), + capabilities => ({ zoom: capabilities.zoom.min - 1 }), + capabilities => ({ torch: true }), +]; + +for (key in constraintGenerators) { + generate_tests( + makePromiseTest, [[ + 'MediaStreamTrack.applyConstraints(constraints) rejects with bad' + + ' constraints, #' + key, + constraintGenerators[key] + ]]); +} + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints.https.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints.https.html new file mode 100644 index 0000000000..bfbf04afdb --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-applyConstraints.https.html @@ -0,0 +1,112 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script> +<script> + +const meteringModeNames = ['none', 'manual', 'single-shot', 'continuous']; + +// This test verifies that we can all MediaStreamTrack.applyConstraints(), with +// a mock Mojo service implementation. + +image_capture_test(async (t, imageCaptureTest) => { + await test_driver.set_permission({name: 'camera', panTiltZoom: true}, + 'granted'); + + const constraints = { advanced : [{ whiteBalanceMode : 'single-shot', + exposureMode : 'manual', + focusMode : 'single-shot', + + pointsOfInterest : [{x : 0.1, y : 0.2}, + {x : 0.3, y : 0.4}], + + exposureCompensation : 133.77, + exposureTime : 10000, + colorTemperature : 6000, + iso : 120.0, + + brightness : 3, + contrast : 4, + saturation : 5, + sharpness : 6, + focusDistance : 7, + + pan : 8, + tilt : 9, + zoom : 3.141592, + + torch : true + }]}; + + let stream = await navigator.mediaDevices.getUserMedia({video: true}); + let videoTrack = stream.getVideoTracks()[0]; + + try { + await videoTrack.applyConstraints(constraints); + } catch (error) { + assert_unreached('applyConstraints(): ' + error.message); + } + + const constraintsDict = constraints.advanced[0]; + let appliedConstraints = videoTrack.getConstraints(); + const appliedConstraintsDict = appliedConstraints.advanced[0]; + + // Check that |appliedConstraints| and |constraints| are equal. + assert_equals(constraintsDict.length, appliedConstraintsDict.length); + Object.keys(appliedConstraintsDict).forEach((key, value) => { + assert_not_equals(constraintsDict[key], undefined, 'key ' + key); + if (key != 'pointsOfInterest') { + assert_equals(constraintsDict[key], appliedConstraintsDict[key], key); + } else { + assert_point2d_array_approx_equals(constraintsDict[key], + appliedConstraintsDict[key], 0.01); + } + }); + + let theMock = imageCaptureTest.mockImageCapture(); + assert_equals(constraintsDict.whiteBalanceMode, + meteringModeNames[theMock.options().whiteBalanceMode], + 'whiteBalanceMode'); + assert_equals(constraintsDict.exposureMode, + meteringModeNames[theMock.options().exposureMode], + 'exposureMode'); + assert_equals(constraintsDict.focusMode, + meteringModeNames[theMock.options().focusMode], + 'focusMode'); + + assert_point2d_array_approx_equals(constraintsDict.pointsOfInterest, + theMock.options().pointsOfInterest, + 0.01); + + assert_equals(constraintsDict.exposureCompensation, + theMock.options().exposureCompensation, + 'exposureCompensation'); + assert_equals(constraintsDict.exposureTime, + theMock.options().exposureTime, + 'exposureTime'); + assert_equals(constraintsDict.colorTemperature, + theMock.options().colorTemperature, 'colorTemperature'); + assert_equals(constraintsDict.iso, theMock.options().iso, 'iso'); + + assert_equals(constraintsDict.brightness, theMock.options().brightness, + 'brightness'); + assert_equals(constraintsDict.contrast, theMock.options().contrast, + 'constrast'); + assert_equals(constraintsDict.saturation, theMock.options().saturation, + 'saturation'); + assert_equals(constraintsDict.sharpness, theMock.options().sharpness, + 'sharpness'); + assert_equals(constraintsDict.focusDistance, theMock.options().focusDistance + ,'focusDistance'); + + assert_equals(constraintsDict.pan, theMock.options().pan, 'pan'); + assert_equals(constraintsDict.tilt, theMock.options().tilt, 'tilt'); + assert_equals(constraintsDict.zoom, theMock.options().zoom, 'zoom'); + + assert_equals(constraintsDict.torch, theMock.options().torch, 'torch'); + +}, 'exercises MediaStreamTrack.applyConstraints(constraints)'); + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-clone.https.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-clone.https.html new file mode 100644 index 0000000000..3ee21031eb --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-clone.https.html @@ -0,0 +1,362 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script> +<script> + +// This test verifies that we can set some nondefault constraints, then clone a +// MediaStreamTrack and check that the cloned constraints are the same as the +// original, with a mock Mojo service implementation. +image_capture_test(async (t, imageCaptureTest) => { + await test_driver.set_permission({name: 'camera', panTiltZoom: true}, + 'granted'); + + const constraints = { advanced : [{ whiteBalanceMode : 'single-shot', + exposureMode : 'manual', + focusMode : 'single-shot', + + pointsOfInterest : [{x : 0.1, y : 0.2}, + {x : 0.3, y : 0.4}], + + exposureCompensation : 133.77, + exposureTime : 10000, + colorTemperature : 6000, + iso : 120.0, + + brightness : 3, + contrast : 4, + saturation : 5, + sharpness : 6, + focusDistance : 7, + + pan : 8, + tilt : 9, + zoom : 3.141592, + + torch : true + }]}; + + let stream = await navigator.mediaDevices.getUserMedia({video: true}); + let originalVideoTrack = stream.getVideoTracks()[0]; + + await originalVideoTrack.applyConstraints(constraints); + + let appliedConstraints = originalVideoTrack.getConstraints(); + + let clonedVideoTrack = originalVideoTrack.clone(); + let appliedClonedConstraints = clonedVideoTrack.getConstraints(); + assert_true('advanced' in appliedClonedConstraints); + assert_equals(appliedClonedConstraints.advanced.length, 1); + const appliedClonedAdvancedConstraints = appliedClonedConstraints.advanced[0]; + + // Check that |appliedClonedAdvancedConstraints| and |appliedAdvancedConstraints| are equal. + const appliedAdvancedConstraints = appliedConstraints.advanced[0]; + assert_equals(appliedAdvancedConstraints.length, appliedClonedAdvancedConstraints.length); + Object.keys(appliedClonedAdvancedConstraints).forEach((key, value) => { + assert_not_equals(appliedAdvancedConstraints[key], undefined, 'key ' + key); + if (key != 'pointsOfInterest') { + assert_equals(appliedAdvancedConstraints[key], appliedClonedAdvancedConstraints[key], key); + } else { + assert_point2d_array_approx_equals(appliedAdvancedConstraints[key], + appliedClonedAdvancedConstraints[key], 0.01); + } + }); + + assert_equals(appliedAdvancedConstraints.whiteBalanceMode, + appliedClonedAdvancedConstraints.whiteBalanceMode, + 'whiteBalanceMode'); + assert_equals(appliedAdvancedConstraints.exposureMode, + appliedClonedAdvancedConstraints.exposureMode, + 'exposureMode'); + assert_equals(appliedAdvancedConstraints.focusMode, + appliedClonedAdvancedConstraints.focusMode, + 'focusMode'); + + assert_point2d_array_approx_equals( + appliedAdvancedConstraints.pointsOfInterest, + appliedClonedAdvancedConstraints.pointsOfInterest, + 0.01); + + assert_equals(appliedAdvancedConstraints.exposureCompensation, + appliedClonedAdvancedConstraints.exposureCompensation, + 'exposureCompensation'); + assert_equals(appliedAdvancedConstraints.exposureTime, + appliedClonedAdvancedConstraints.exposureTime, + 'exposureTime'); + assert_equals(appliedAdvancedConstraints.colorTemperature, + appliedClonedAdvancedConstraints.colorTemperature, + 'colorTemperature'); + assert_equals(appliedAdvancedConstraints.iso, + appliedClonedAdvancedConstraints.iso, + 'iso'); + assert_equals(appliedAdvancedConstraints.brightness, + appliedClonedAdvancedConstraints.brightness, + 'brightness'); + assert_equals(appliedAdvancedConstraints.contrast, + appliedClonedAdvancedConstraints.contrast, + 'constrast'); + assert_equals(appliedAdvancedConstraints.saturation, + appliedClonedAdvancedConstraints.saturation, + 'saturation'); + assert_equals(appliedAdvancedConstraints.sharpness, + appliedClonedAdvancedConstraints.sharpness, + 'sharpness'); + assert_equals(appliedAdvancedConstraints.focusDistance, + appliedClonedAdvancedConstraints.focusDistance, + 'focusDistance'); + + assert_equals(appliedAdvancedConstraints.pan, + appliedClonedAdvancedConstraints.pan, + 'pan'); + assert_equals(appliedAdvancedConstraints.tilt, + appliedClonedAdvancedConstraints.tilt, + 'tilt'); + assert_equals(appliedAdvancedConstraints.zoom, + appliedClonedAdvancedConstraints.zoom, + 'zoom'); + + assert_equals(appliedAdvancedConstraints.torch, + appliedClonedAdvancedConstraints.torch, + 'torch'); +}, 'checks MediaStreamTrack.clone() gets same applied constraints'); + +// This test verifies that MediaStreamTrack ImageCapture settings are copied +// when cloning a MediaStreamTrack. +image_capture_test(async (t, imageCaptureTest) => { + await test_driver.set_permission({name: 'camera', panTiltZoom: true}, + 'granted'); + + let stream = await navigator.mediaDevices.getUserMedia({video: true}); + let originalVideoTrack = stream.getVideoTracks()[0]; + let clonedVideoTrack = originalVideoTrack.clone(); + + let settings = originalVideoTrack.getSettings(); + let clonedSettings = clonedVideoTrack.getSettings(); + + assert_equals(settings.whiteBalanceMode, + clonedSettings.whiteBalanceMode, + 'whiteBalanceMode'); + assert_equals(settings.exposureMode, + clonedSettings.exposureMode, + 'exposureMode;'); + assert_equals(settings.focusMode, + clonedSettings.focusMode, + 'focusMode'); + + assert_point2d_array_approx_equals( + settings.pointsOfInterest, + clonedSettings.pointsOfInterest, + 0.01); + + assert_equals(settings.exposureCompensation, + clonedSettings.exposureCompensation, + 'exposureCompensation'); + assert_equals(settings.exposureTime, + clonedSettings.exposureTime, + 'exposureTime'); + assert_equals(settings.colorTemperature, + clonedSettings.colorTemperature, + 'colorTemperature'); + assert_equals(settings.iso, + clonedSettings.iso, + 'iso'); + + assert_equals(settings.brightness, + clonedSettings.brightness, + 'brightness'); + assert_equals(settings.contrast, + clonedSettings.contrast, + 'contrast'); + assert_equals(settings.saturation, + clonedSettings.saturation, + 'saturation'); + assert_equals(settings.sharpness, + clonedSettings.sharpness, + 'sharpness'); + + assert_equals(settings.focusDistance, + clonedSettings.focusDistance, + 'focusDistance'); + + assert_equals(settings.pan, + clonedSettings.pan, + 'pan'); + assert_equals(settings.tilt, + clonedSettings.tilt, + 'tilt'); + assert_equals(settings.zoom, + clonedSettings.zoom, + 'zoom'); + + assert_equals(settings.torch, + clonedSettings.torch, + 'torch'); +}, 'checks MediaStreamTrack.clone() gets same settings'); + +// This test verifies that MediaStreamTrack ImageCapture capabilities are copied +// when cloning a MediaStreamTrack. +image_capture_test(async (t, imageCaptureTest) => { + await test_driver.set_permission({name: 'camera', panTiltZoom: true}, + 'granted'); + + let stream = await navigator.mediaDevices.getUserMedia({video: true}); + let originalVideoTrack = stream.getVideoTracks()[0]; + let clonedVideoTrack = originalVideoTrack.clone(); + + let capabilities = originalVideoTrack.getCapabilities(); + let clonedCapabilities = clonedVideoTrack.getCapabilities(); + + assert_equals(capabilities.whiteBalanceMode.length, + clonedCapabilities.whiteBalanceMode.length, + 'whiteBalanceMode length'); + for (i = 0; i < capabilities.whiteBalanceMode.length; ++i) { + assert_equals(capabilities.whiteBalanceMode[i], + clonedCapabilities.whiteBalanceMode[i], + 'whiteBalanceMode'); + } + + assert_equals(capabilities.exposureMode.length, + clonedCapabilities.exposureMode.length, + 'exposureMode length'); + for (i = 0; i < capabilities.exposureMode.length; ++i) { + assert_equals(capabilities.exposureMode[i], + clonedCapabilities.exposureMode[i], + 'exposureMode'); + } + + assert_equals(capabilities.focusMode.length, + clonedCapabilities.focusMode.length, + 'focusMode length'); + for (i = 0; i < capabilities.focusMode.length; ++i) { + assert_equals(capabilities.focusMode[i], + clonedCapabilities.focusMode[i], + 'focusMode'); + } + + assert_equals(capabilities.exposureCompensation.max, + clonedCapabilities.exposureCompensation.max, + 'exposureCompensation max'); + assert_equals(capabilities.exposureCompensation.min, + clonedCapabilities.exposureCompensation.min, + 'exposureCompensation min'); + assert_equals(capabilities.exposureCompensation.step, + clonedCapabilities.exposureCompensation.step, + 'exposureCompensation step'); + + assert_equals(capabilities.exposureTime.max, + clonedCapabilities.exposureTime.max, + 'exposureTime max'); + assert_equals(capabilities.exposureTime.min, + clonedCapabilities.exposureTime.min, + 'exposureTime min'); + assert_equals(capabilities.exposureTime.step, + clonedCapabilities.exposureTime.step, + 'exposureTime step'); + + assert_equals(capabilities.colorTemperature.max, + clonedCapabilities.colorTemperature.max, + 'colorTemperature max'); + assert_equals(capabilities.colorTemperature.min, + clonedCapabilities.colorTemperature.min, + 'colorTemperature min'); + assert_equals(capabilities.colorTemperature.step, + clonedCapabilities.colorTemperature.step, + 'colorTemperature step'); + + assert_equals(capabilities.iso.max, + clonedCapabilities.iso.max, + 'iso max'); + assert_equals(capabilities.iso.min, + clonedCapabilities.iso.min, + 'iso min'); + assert_equals(capabilities.iso.step, + clonedCapabilities.iso.step, + 'iso step'); + + assert_equals(capabilities.brightness.max, + clonedCapabilities.brightness.max, + 'brightness max'); + assert_equals(capabilities.brightness.min, + clonedCapabilities.brightness.min, + 'brightness min'); + assert_equals(capabilities.brightness.step, + clonedCapabilities.brightness.step, + 'brightness step'); + + assert_equals(capabilities.contrast.max, + clonedCapabilities.contrast.max, + 'contrast max'); + assert_equals(capabilities.contrast.min, + clonedCapabilities.contrast.min, + 'contrast min'); + assert_equals(capabilities.contrast.step, + clonedCapabilities.contrast.step, + 'contrast step'); + + assert_equals(capabilities.saturation.max, + clonedCapabilities.saturation.max, + 'saturation max'); + assert_equals(capabilities.saturation.min, + clonedCapabilities.saturation.min, + 'saturation min'); + assert_equals(capabilities.saturation.step, + clonedCapabilities.saturation.step, + 'saturation step'); + + assert_equals(capabilities.sharpness.max, + clonedCapabilities.sharpness.max, + 'sharpness max'); + assert_equals(capabilities.sharpness.min, + clonedCapabilities.sharpness.min, + 'sharpness min'); + assert_equals(capabilities.sharpness.step, + clonedCapabilities.sharpness.step, + 'sharpness step'); + + assert_equals(capabilities.focusDistance.max, + clonedCapabilities.focusDistance.max, + 'focusDistance max'); + assert_equals(capabilities.focusDistance.min, + clonedCapabilities.focusDistance.min, + 'focusDistance min'); + assert_equals(capabilities.focusDistance.step, + clonedCapabilities.focusDistance.step, + 'focusDistance step'); + + assert_equals(capabilities.pan.max, + clonedCapabilities.pan.max, + 'pan max'); + assert_equals(capabilities.pan.min, + clonedCapabilities.pan.min, + 'pan min'); + assert_equals(capabilities.pan.step, + clonedCapabilities.pan.step, + 'pan step'); + + assert_equals(capabilities.tilt.max, + clonedCapabilities.tilt.max, + 'tilt max'); + assert_equals(capabilities.tilt.min, + clonedCapabilities.tilt.min, + 'tilt min'); + assert_equals(capabilities.tilt.step, + clonedCapabilities.tilt.step, + 'tilt step'); + + assert_equals(capabilities.zoom.max, + clonedCapabilities.zoom.max, + 'zoom max'); + assert_equals(capabilities.zoom.min, + clonedCapabilities.zoom.min, + 'zoom min'); + assert_equals(capabilities.zoom.step, + clonedCapabilities.zoom.step, + 'zoom step'); + + assert_equals(capabilities.torch, + clonedCapabilities.torch, + 'torch'); +}, 'checks MediaStreamTrack.clone() gets same capabilities'); +</script> diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getCapabilities-fast.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getCapabilities-fast.html new file mode 100644 index 0000000000..55272d1499 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getCapabilities-fast.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<canvas id='canvas' width=10 height=10/> +</body> +<script> + +// This test verifies that MediaStreamTrack.getCapabilities() exists and that it +// returns something. Other tests go deeper. +test(function() { + var canvas = document.getElementById('canvas'); + var context = canvas.getContext("2d"); + context.fillStyle = "red"; + context.fillRect(0, 0, 10, 10); + + var stream = canvas.captureStream(); + assert_equals(stream.getAudioTracks().length, 0); + assert_equals(stream.getVideoTracks().length, 1); + + var videoTrack = stream.getVideoTracks()[0]; + + assert_equals(typeof videoTrack.getCapabilities, 'function'); + + capabilities = videoTrack.getCapabilities(); + assert_equals(typeof capabilities, 'object'); +}, 'MediaStreamTrack.getCapabilities()'); + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getCapabilities.https.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getCapabilities.https.html new file mode 100644 index 0000000000..6a4835a475 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getCapabilities.https.html @@ -0,0 +1,159 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script> +<script> + +const meteringModeNames = ['none', 'manual', 'single-shot', 'continuous']; + +// This test verifies that MediaTrackCapabilities are returned upon +// MediaStreamTrack.getCapabilities(), with a mock Mojo service implementation. +// When PTZ permission is denied though, PTZ capabilities are not available. + +function makeImageCaptureTest(hasPanTiltZoomPermissionGranted) { + image_capture_test(async (t, imageCaptureTest) => { + const ptzPermission = hasPanTiltZoomPermissionGranted ? 'granted' : 'denied'; + await test_driver.set_permission({name: 'camera', panTiltZoom: true}, + ptzPermission); + + let mockCapabilities = imageCaptureTest.mockImageCapture().state(); + + // |stream| must be created _after_ |mock| is constructed to give the + // latter time to override the bindings. + let stream = await navigator.mediaDevices.getUserMedia({video: true}); + assert_equals(stream.getAudioTracks().length, 0); + assert_equals(stream.getVideoTracks().length, 1); + + let videoTrack = stream.getVideoTracks()[0]; + assert_equals(typeof videoTrack.getCapabilities, 'function'); + + let capabilities = videoTrack.getCapabilities(); + assert_equals(typeof capabilities, 'object'); + + assert_equals(capabilities.whiteBalanceMode.length, + mockCapabilities.supportedWhiteBalanceModes.length, + 'whiteBalanceMode'); + for (i = 0; i < capabilities.whiteBalanceMode.length; ++i) { + assert_equals( + capabilities.whiteBalanceMode[i], + meteringModeNames[mockCapabilities + .supportedWhiteBalanceModes[i]], + 'whiteBalanceMode'); + } + + assert_equals(capabilities.exposureMode.length, + mockCapabilities.supportedExposureModes.length, + 'exposureMode'); + for (i = 0; i < capabilities.exposureMode.length; ++i) { + assert_equals( + capabilities.exposureMode[i], + meteringModeNames[mockCapabilities.supportedExposureModes[i]], + 'exposureMode'); + } + + assert_equals(capabilities.focusMode.length, + mockCapabilities.supportedFocusModes.length, + 'focusMode'); + for (i = 0; i < capabilities.focusMode.length; ++i) { + assert_equals( + capabilities.focusMode[i], + meteringModeNames[mockCapabilities.supportedFocusModes[i]], + 'focusMode'); + } + + assert_equals(capabilities.exposureCompensation.max, + mockCapabilities.exposureCompensation.max); + assert_equals(capabilities.exposureCompensation.min, + mockCapabilities.exposureCompensation.min); + assert_equals(capabilities.exposureCompensation.step, + mockCapabilities.exposureCompensation.step); + + assert_equals(capabilities.exposureTime.max, + mockCapabilities.exposureTime.max); + assert_equals(capabilities.exposureTime.min, + mockCapabilities.exposureTime.min); + assert_equals(capabilities.exposureTime.step, + mockCapabilities.exposureTime.step); + + assert_equals(capabilities.colorTemperature.max, + mockCapabilities.colorTemperature.max); + assert_equals(capabilities.colorTemperature.min, + mockCapabilities.colorTemperature.min); + assert_equals(capabilities.colorTemperature.step, + mockCapabilities.colorTemperature.step); + + assert_equals(capabilities.iso.max, mockCapabilities.iso.max); + assert_equals(capabilities.iso.min, mockCapabilities.iso.min); + assert_equals(capabilities.iso.step, mockCapabilities.iso.step); + + assert_equals(capabilities.brightness.max, + mockCapabilities.brightness.max); + assert_equals(capabilities.brightness.min, + mockCapabilities.brightness.min); + assert_equals(capabilities.brightness.step, + mockCapabilities.brightness.step); + + assert_equals(capabilities.contrast.max, + mockCapabilities.contrast.max); + assert_equals(capabilities.contrast.min, + mockCapabilities.contrast.min); + assert_equals(capabilities.contrast.step, + mockCapabilities.contrast.step); + + assert_equals(capabilities.saturation.max, + mockCapabilities.saturation.max); + assert_equals(capabilities.saturation.min, + mockCapabilities.saturation.min); + assert_equals(capabilities.saturation.step, + mockCapabilities.saturation.step); + + assert_equals(capabilities.sharpness.max, + mockCapabilities.sharpness.max); + assert_equals(capabilities.sharpness.min, + mockCapabilities.sharpness.min); + assert_equals(capabilities.sharpness.step, + mockCapabilities.sharpness.step); + + assert_equals(capabilities.focusDistance.max, + mockCapabilities.focusDistance.max); + assert_equals(capabilities.focusDistance.min, + mockCapabilities.focusDistance.min); + assert_equals(capabilities.focusDistance.step, + mockCapabilities.focusDistance.step); + + if (ptzPermission === 'granted') { + assert_equals(capabilities.pan.max, mockCapabilities.pan.max); + assert_equals(capabilities.pan.min, mockCapabilities.pan.min); + assert_equals(capabilities.pan.step, mockCapabilities.pan.step); + + assert_equals(capabilities.tilt.max, mockCapabilities.tilt.max); + assert_equals(capabilities.tilt.min, mockCapabilities.tilt.min); + assert_equals(capabilities.tilt.step, mockCapabilities.tilt.step); + + assert_equals(capabilities.zoom.max, mockCapabilities.zoom.max); + assert_equals(capabilities.zoom.min, mockCapabilities.zoom.min); + assert_equals(capabilities.zoom.step, mockCapabilities.zoom.step); + } else if (ptzPermission === 'denied') { + assert_false('pan' in capabilities); + assert_false('tilt' in capabilities); + assert_false('zoom' in capabilities); + } + + assert_equals(capabilities.torch, mockCapabilities.supportsTorch, + 'torch'); + }); +} + +generate_tests(makeImageCaptureTest, [ + [ + "exercises MediaStreamTrack.getCapabilities() with PTZ permission denied", + false, + ], + [ + "exercises MediaStreamTrack.getCapabilities() with PTZ permission granted", + true, + ], +]); +</script> diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getConstraints.https.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getConstraints.https.html new file mode 100644 index 0000000000..70cd2f2b07 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getConstraints.https.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script> +<script> + +const constraints = { whiteBalanceMode : "single-shot", + exposureMode : "manual", + focusMode : "single-shot", + + exposureCompensation : 133.77, + exposureTime : 10000, // in nano-seconds. + colorTemperature : 6000, + iso : 120.0, + + brightness : 3, + contrast : 4, + saturation : 5, + sharpness : 6, + focusDistance : 7, + + pan : 8, + tilt : 9, + zoom : 3.141592 + // TODO: torch https://crbug.com/700607. + }; + +// These tests verify that MediaStreamTrack.getConstraints() exists and that, +// returns the constraints passed beforehand with applyConstraints. +function makePromiseTest(constraint) { + image_capture_test(async function(t) { + await test_driver.set_permission({name: 'camera', panTiltZoom: true}, + 'granted'); + + let stream = await navigator.mediaDevices.getUserMedia({video: true}); + let videoTrack = stream.getVideoTracks()[0]; + + let constraintsIn = {advanced : [ constraint ]}; + await videoTrack.applyConstraints(constraintsIn); + assert_object_equals(videoTrack.getConstraints(), constraintsIn, "constraints"); + + // Clear constraints by sending an empty constraint set. + await videoTrack.applyConstraints({}); + assert_object_equals(videoTrack.getConstraints(), {}, "constraints"); + }); +}; + +// Send each line of |constraints| in turn and then the whole dictionary. +for (key in constraints) { + let one_constraint = {}; + one_constraint[key] = constraints[key]; + generate_tests( + makePromiseTest, + [[ 'MediaStreamTrack.getConstraints(), key: ' + key, one_constraint ]]); +} + +generate_tests(makePromiseTest, [ + ["MediaStreamTrack.getConstraints(), complete ", constraints], +]); + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getSettings-fast.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getSettings-fast.html new file mode 100644 index 0000000000..a4ecbe6118 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getSettings-fast.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<canvas id='canvas' width=10 height=10/> +</body> +<script> + +// This test verifies that MediaStreamTrack.getSettings() exists and that it +// returns something. Other tests go deeper. +test(function() { + var canvas = document.getElementById('canvas'); + var context = canvas.getContext("2d"); + context.fillStyle = "red"; + context.fillRect(0, 0, 10, 10); + + var stream = canvas.captureStream(); + assert_equals(stream.getAudioTracks().length, 0); + assert_equals(stream.getVideoTracks().length, 1); + + var videoTrack = stream.getVideoTracks()[0]; + + assert_equals(typeof videoTrack.getSettings, 'function'); + + settings = videoTrack.getSettings(); + assert_equals(typeof settings, 'object'); +}, 'MediaStreamTrack.getSettings()'); + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getSettings.https.html b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getSettings.https.html new file mode 100644 index 0000000000..bd8a1ea100 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/MediaStreamTrack-getSettings.https.html @@ -0,0 +1,89 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script> +<script> + +const meteringModeNames = ['none', 'manual', 'single-shot', 'continuous']; + +// This test verifies that the settings defined in the mock Mojo service +// implementation are the same as those returned by the corresponding +// MediaStreamTrack.getSettings(), except for PTZ settings when PTZ +// permission is denied. + +function makeImageCaptureTest(hasPanTiltZoomPermissionGranted) { + image_capture_test(async (t, imageCaptureTest) => { + const ptzPermission = hasPanTiltZoomPermissionGranted ? 'granted' : 'denied'; + await test_driver.set_permission({name: 'camera', panTiltZoom: true}, + ptzPermission); + + let mockSettings = imageCaptureTest.mockImageCapture().state(); + + // |stream| must be created _after_ |mock| is constructed to give the + // latter time to override the bindings. + let stream = await navigator.mediaDevices.getUserMedia({video: true}); + let videoTrack = stream.getVideoTracks()[0]; + + // |videoTrack|s settings retrieval, just like the actual capture, is a + // process kicked right after creation, we introduce a small delay to + // allow for those to be collected. + await new Promise(resolve => step_timeout(resolve, 100)); + + let settings = videoTrack.getSettings(); + assert_equals(typeof settings, 'object'); + + assert_equals(settings.whiteBalanceMode, + meteringModeNames[mockSettings.currentWhiteBalanceMode], + 'whiteBalanceMode'); + assert_equals(settings.exposureMode, + meteringModeNames[mockSettings.currentExposureMode], + 'exposureMode;'); + assert_equals(settings.focusMode, + meteringModeNames[mockSettings.currentFocusMode], + 'focusMode'); + + assert_point2d_array_approx_equals( + settings.pointsOfInterest, mockSettings.pointsOfInterest, 0.01); + + assert_equals(settings.exposureCompensation, + mockSettings.exposureCompensation.current); + assert_equals(settings.exposureTime, + mockSettings.exposureTime.current); + assert_equals(settings.colorTemperature, + mockSettings.colorTemperature.current); + assert_equals(settings.iso, mockSettings.iso.current); + + assert_equals(settings.brightness, mockSettings.brightness.current); + assert_equals(settings.contrast, mockSettings.contrast.current); + assert_equals(settings.saturation, mockSettings.saturation.current); + assert_equals(settings.sharpness, mockSettings.sharpness.current); + + assert_equals(settings.focusDistance, mockSettings.focusDistance.current); + + if (ptzPermission === 'granted') { + assert_equals(settings.pan, mockSettings.pan.current); + assert_equals(settings.tilt, mockSettings.tilt.current); + assert_equals(settings.zoom, mockSettings.zoom.current); + } else if (ptzPermission === 'denied') { + assert_false('pan' in settings); + assert_false('tilt' in settings); + assert_false('zoom' in settings); + } + + assert_equals(settings.torch, mockSettings.torch, 'torch'); + }); +} + +generate_tests(makeImageCaptureTest, [ + [ + "exercises MediaStreamTrack.getSettings() with PTZ permission denied", + false, + ], + [ + "exercises MediaStreamTrack.getSettings() with PTZ permission granted", + true, + ], +]); +</script> diff --git a/testing/web-platform/tests/mediacapture-image/detached-HTMLCanvasElement.html b/testing/web-platform/tests/mediacapture-image/detached-HTMLCanvasElement.html new file mode 100644 index 0000000000..e27950fc5e --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/detached-HTMLCanvasElement.html @@ -0,0 +1,26 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<script> + +async_test(t => { + let iframe = document.createElement('iframe'); + let html = "<canvas id='canvas' width=10 height=10 />"; + iframe.srcdoc = html; + iframe.onload = t.step_func_done(() => { + // This detaches the frame while retaining a reference to an + // HTMLCanvasElement from it. + let canvas = iframe.contentWindow.document.getElementById('canvas'); + document.body.removeChild(iframe); + + // Creation of the ImageCapture object (as part of the MediaStreamTrack) + // should be safe even if the frame is detached. + canvas.captureStream(); + }); + + document.body.appendChild(iframe); +}, 'MediaStreamTrack can be obtained from a detached frame'); + +</script> +</body> diff --git a/testing/web-platform/tests/mediacapture-image/getPhotoCapabilities.html b/testing/web-platform/tests/mediacapture-image/getPhotoCapabilities.html new file mode 100644 index 0000000000..94962a03f4 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/getPhotoCapabilities.html @@ -0,0 +1,73 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script> +<body> +<canvas id='canvas' width=10 height=10/> +</body> +<script> + +// This test verifies that ImageCapture can get PhotoCapabilities(), with a mock +// Mojo interface implementation. + +image_capture_test(async (t, imageCaptureTest) => { + let canvas = document.getElementById('canvas'); + let context = canvas.getContext('2d'); + context.fillStyle = 'red'; + context.fillRect(0, 0, 10, 10); + let stream = canvas.captureStream(); + + let mockCapabilities = imageCaptureTest.mockImageCapture().state(); + let capturer = new ImageCapture(stream.getVideoTracks()[0]); + assert_equals(typeof capturer.getPhotoCapabilities, 'function'); + + let capabilities = await capturer.getPhotoCapabilities(); + + assert_equals(capabilities.redEyeReduction, 'controllable', + 'redEyeReduction'); + + assert_equals(capabilities.imageHeight.max, mockCapabilities.height.max); + assert_equals(capabilities.imageHeight.min, mockCapabilities.height.min); + assert_equals(capabilities.imageHeight.step, mockCapabilities.height.step); + + assert_equals(capabilities.imageWidth.max, mockCapabilities.width.max); + assert_equals(capabilities.imageWidth.min, mockCapabilities.width.min); + assert_equals(capabilities.imageWidth.step, mockCapabilities.width.step); + + assert_array_equals(capabilities.fillLightMode, [ 'auto', 'flash' ], + 'fillLightMode'); + +}, 'exercises ImageCapture.getPhotoCapabilities()'); + +promise_test(t => { + let canvas = document.getElementById('canvas'); + let context = canvas.getContext('2d'); + context.fillStyle = 'red'; + context.fillRect(0, 0, 10, 10); + let stream = canvas.captureStream(); + let videoTrack = stream.getVideoTracks()[0]; + videoTrack.stop(); + + let capturer = new ImageCapture(videoTrack); + assert_equals(videoTrack.readyState, 'ended'); + + return promise_rejects_dom(t, 'InvalidStateError', capturer.getPhotoCapabilities()) + +}, 'getPhotoCapabilities() of an ended Track should throw "InvalidStateError"'); + +async_test(t => { + let canvas = document.getElementById('canvas'); + let context = canvas.getContext('2d'); + context.fillStyle = 'red'; + context.fillRect(0, 0, 10, 10); + let stream = canvas.captureStream(); + let videoTrack = stream.getVideoTracks()[0]; + + let capturer = new ImageCapture(videoTrack); + capturer.getPhotoCapabilities() + .then(t.step_func_done(() => assert_unreached('should throw "OperationError"'))) + .catch(t.step_func_done(e => assert_equals(e.name, 'OperationError'))) + videoTrack.stop(); +}, 'throw "OperationError" when the MediaStreamTrack is stopped while getting photo capabilities'); + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/getPhotoSettings.html b/testing/web-platform/tests/mediacapture-image/getPhotoSettings.html new file mode 100644 index 0000000000..4540373559 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/getPhotoSettings.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script> +<body> +<canvas id='canvas' width=10 height=10/> +</body> +<script> + +const fillLightModeNames = ['off', 'auto', 'flash']; + +// This test verifies that ImageCapture can call getPhotoSettings(), with a +// mock Mojo interface implementation. +image_capture_test(async (t, imageCaptureTest) => { + let canvas = document.getElementById('canvas'); + let context = canvas.getContext('2d'); + context.fillStyle = 'red'; + context.fillRect(0, 0, 10, 10); + let stream = canvas.captureStream(); + + let mockState = imageCaptureTest.mockImageCapture().state(); + let capturer = new ImageCapture(stream.getVideoTracks()[0]); + let settings = await capturer.getPhotoSettings(); + assert_equals(settings.imageWidth, mockState.width.current, 'width'); + assert_equals(settings.imageHeight, mockState.height.current, 'height'); + // TODO(mcasas): check the remaining two entries https://crbug.com/732521. + +}, 'exercises ImageCapture.getPhotoSettings()'); + +promise_test(t => { + let canvas = document.getElementById('canvas'); + let context = canvas.getContext('2d'); + context.fillStyle = 'red'; + context.fillRect(0, 0, 10, 10); + let stream = canvas.captureStream(); + let videoTrack = stream.getVideoTracks()[0]; + videoTrack.stop(); + + let capturer = new ImageCapture(videoTrack); + assert_equals(videoTrack.readyState, 'ended'); + + return promise_rejects_dom(t, 'InvalidStateError', capturer.getPhotoSettings()) + +}, 'getPhotoSettings() of an ended Track should throw "InvalidStateError"'); + +async_test(t => { + let canvas = document.getElementById('canvas'); + let context = canvas.getContext('2d'); + context.fillStyle = 'red'; + context.fillRect(0, 0, 10, 10); + let stream = canvas.captureStream(); + let videoTrack = stream.getVideoTracks()[0]; + + let capturer = new ImageCapture(videoTrack); + capturer.getPhotoSettings() + .then(t.step_func_done(() => assert_unreached('should throw "OperationError"'))) + .catch(t.step_func_done(e => assert_equals(e.name, 'OperationError'))) + videoTrack.stop(); +}, 'throw "OperationError" when the MediaStreamTrack is stopped while getting photo settings'); + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/getusermedia.https.html b/testing/web-platform/tests/mediacapture-image/getusermedia.https.html new file mode 100644 index 0000000000..033501cf64 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/getusermedia.https.html @@ -0,0 +1,26 @@ +<!doctype html> +<meta charset=utf-8> +<title>getUserMedia</title> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> +"use strict"; + + [ + { video: { pan: { min: 1 } } }, + { video: { pan: { max: 1 } } }, + { video: { pan: { exact: 1 } } }, + { video: { tilt: { min: 1 } } }, + { video: { tilt: { max: 1 } } }, + { video: { tilt: { exact: 1 } } }, + { video: { zoom: { min: 1 } } }, + { video: { zoom: { max: 1 } } }, + { video: { zoom: { exact: 1 } } } + ].forEach(constraints => + promise_test(t => { + const promise = navigator.mediaDevices.getUserMedia(constraints); + return promise_rejects_js(t, TypeError, promise); + }, `getUserMedia(${JSON.stringify(constraints)}) must fail with TypeError`) + ); + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/idlharness.window.js b/testing/web-platform/tests/mediacapture-image/idlharness.window.js new file mode 100644 index 0000000000..2977138647 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/idlharness.window.js @@ -0,0 +1,25 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: timeout=long + +// https://w3c.github.io/mediacapture-image/ + +'use strict'; + +idl_test( + ['image-capture'], + ['mediacapture-streams', 'html', 'dom'], + idl_array => { + idl_array.add_objects({ + ImageCapture : ['capture'], + }); + + const canvas = document.createElement('canvas'); + document.body.appendChild(canvas); + const context = canvas.getContext("2d"); + context.fillStyle = "red"; + context.fillRect(0, 0, 10, 10); + const track = canvas.captureStream().getVideoTracks()[0]; + self.capture = new ImageCapture(track); + } +); diff --git a/testing/web-platform/tests/mediacapture-image/resources/imagecapture-helpers.js b/testing/web-platform/tests/mediacapture-image/resources/imagecapture-helpers.js new file mode 100644 index 0000000000..8f142cff41 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/resources/imagecapture-helpers.js @@ -0,0 +1,55 @@ +'use strict'; + +// These tests rely on the User Agent providing an implementation of +// platform image capture backends. +// +// In Chromium-based browsers this implementation is provided by a polyfill +// in order to reduce the amount of test-only code shipped to users. To enable +// these tests the browser must be run with these options: +// +// --enable-blink-features=MojoJS,MojoJSTest + +async function loadChromiumResources() { + await import('/resources/chromium/mock-imagecapture.js'); +} + +async function initialize_image_capture_tests() { + if (typeof ImageCaptureTest === 'undefined') { + const script = document.createElement('script'); + script.src = '/resources/test-only-api.js'; + script.async = false; + const p = new Promise((resolve, reject) => { + script.onload = () => { resolve(); }; + script.onerror = e => { reject(e); }; + }) + document.head.appendChild(script); + await p; + + if (isChromiumBased) { + await loadChromiumResources(); + } + } + assert_implements(ImageCaptureTest, 'ImageCaptureTest is unavailable'); + let imageCaptureTest = new ImageCaptureTest(); + await imageCaptureTest.initialize(); + return imageCaptureTest; +} + +function image_capture_test(func, name, properties) { + promise_test(async (t) => { + let imageCaptureTest = await initialize_image_capture_tests(); + try { + await func(t, imageCaptureTest); + } finally { + await imageCaptureTest.reset(); + }; + }, name, properties); +} + +function assert_point2d_array_approx_equals(actual, expected, epsilon) { + assert_equals(actual.length, expected.length, 'length'); + for (var i = 0; i < actual.length; ++i) { + assert_approx_equals(actual[i].x, expected[i].x, epsilon, 'x'); + assert_approx_equals(actual[i].y, expected[i].y, epsilon, 'y'); + } +} diff --git a/testing/web-platform/tests/mediacapture-image/takePhoto-reject.html b/testing/web-platform/tests/mediacapture-image/takePhoto-reject.html new file mode 100644 index 0000000000..4deee97d7b --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/takePhoto-reject.html @@ -0,0 +1,51 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script> +<body> +<canvas id='canvas' width=10 height=10/> +</body> +<script> + +let canvas = document.getElementById('canvas'); +let context = canvas.getContext('2d'); +context.fillStyle = 'red'; +context.fillRect(0, 0, 10, 10); + +// This test verifies that ImageCapture.takePhoto() rejects if any passed +// option is unsupported or outside its allowed range. +function makePromiseTest(getOption) { + image_capture_test(async (t, imageCaptureTest) => { + imageCaptureTest.mockImageCapture().state().redEyeReduction = 0; + + let stream = canvas.captureStream(); + let capturer = new ImageCapture(stream.getVideoTracks()[0]); + await capturer.getPhotoCapabilities(); + const options = getOption(imageCaptureTest.mockImageCapture().state()); + + try { + await capturer.takePhoto(options); + assert_unreached('expected takePhoto to reject'); + } catch (error) { + assert_equals(error.name, 'NotSupportedError'); + } + }); +} + +const optionsGenerators = [ + capabilities => ({ redEyeReduction: true }), + capabilities => ({ imageHeight: capabilities.height.max + 1 }), + capabilities => ({ imageHeight: capabilities.height.min - 1 }), + capabilities => ({ imageWidth: capabilities.width.max + 1 }), + capabilities => ({ imageWidth: capabilities.width.min - 1 }), + capabilities => ({ fillLightMode: 'off' }), +]; + +for (key in optionsGenerators) { + generate_tests( + makePromiseTest, + [[ 'ImageCapture.takePhoto(options) rejects with bad options, #' + key, + optionsGenerators[key] ]]); +} + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/takePhoto-with-PhotoSettings.html b/testing/web-platform/tests/mediacapture-image/takePhoto-with-PhotoSettings.html new file mode 100644 index 0000000000..5870861245 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/takePhoto-with-PhotoSettings.html @@ -0,0 +1,63 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script> +<body> +<canvas id='canvas' width=10 height=10/> +</body> +<script> + +const fillLightModeNames = ['off', 'auto', 'flash']; + +// This test verifies that ImageCapture can call takePhoto with a PhotoSettings +// argument, with a mock Mojo interface implementation. + +image_capture_test(async (t, imageCaptureTest) => { + let canvas = document.getElementById('canvas'); + let context = canvas.getContext('2d'); + context.fillStyle = 'red'; + context.fillRect(0, 0, 10, 10); + let stream = canvas.captureStream(); + + const optionsDict = { imageWidth : 1080, + imageHeight : 100, + redEyeReduction : true, + fillLightMode : 'flash' + }; + + let capturer = new ImageCapture(stream.getVideoTracks()[0]); + let blob = await capturer.takePhoto(optionsDict); + + // JS Blob is almost-opaque, can only check |type| and |size|. + assert_equals(blob.type, 'image/cat'); + assert_equals(blob.size, 2); + + + assert_equals(true, imageCaptureTest.mockImageCapture().options().hasWidth, + 'hasWidth'); + assert_equals(optionsDict.imageWidth, + imageCaptureTest.mockImageCapture().options().width,'width'); + assert_equals(true, imageCaptureTest.mockImageCapture().options().hasHeight, + 'hasHeight'); + assert_equals(optionsDict.imageHeight, + imageCaptureTest.mockImageCapture().options().height, + 'height'); + + // Depending on how mojo boolean packing in integers is arranged, this can + // be a number instead of a boolean, compare directly. + // TODO(mcasas): Revert to assert_equals() when yzshen@ has sorted it out. + assert_true( + optionsDict.redEyeReduction == imageCaptureTest.mockImageCapture(). + options().redEyeReduction, 'redEyeReduction'); + + assert_equals(true, + imageCaptureTest.mockImageCapture().options().hasFillLightMode, + 'hasFillLightMode'); + assert_equals(optionsDict.fillLightMode, + fillLightModeNames[ + imageCaptureTest.mockImageCapture().options().fillLightMode], + 'fillLightMode'); + +}, 'exercises ImageCapture.takePhoto(PhotoSettings dictionary)'); + +</script> diff --git a/testing/web-platform/tests/mediacapture-image/takePhoto-without-PhotoCapabilities.https.window.js b/testing/web-platform/tests/mediacapture-image/takePhoto-without-PhotoCapabilities.https.window.js new file mode 100644 index 0000000000..96eb253ccd --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/takePhoto-without-PhotoCapabilities.https.window.js @@ -0,0 +1,6 @@ +promise_test(async t => { + const track = new MediaStreamTrackGenerator('video'); + const capturer = new ImageCapture(track); + const settings = await capturer.getPhotoSettings(); + await promise_rejects_dom(t, 'UnknownError', capturer.takePhoto(settings)); +}, 'exercise takePhoto() on a track without PhotoCapabilities'); diff --git a/testing/web-platform/tests/mediacapture-image/takePhoto.html b/testing/web-platform/tests/mediacapture-image/takePhoto.html new file mode 100644 index 0000000000..6b27f085a8 --- /dev/null +++ b/testing/web-platform/tests/mediacapture-image/takePhoto.html @@ -0,0 +1,61 @@ +<!DOCTYPE html> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="/mediacapture-image/resources/imagecapture-helpers.js"></script> +<body> +<canvas id='canvas' width=10 height=10/> +</body> +<script> + +// This test verifies that ImageCapture can takePhoto()s, with a mock Mojo +// interface implementation. + +image_capture_test(async t => { + let canvas = document.getElementById('canvas'); + let context = canvas.getContext('2d'); + context.fillStyle = 'red'; + context.fillRect(0, 0, 10, 10); + let stream = canvas.captureStream(); + + let capturer = new ImageCapture(stream.getVideoTracks()[0]); + let blob = await capturer.takePhoto(); + + // JS Blob is almost-opaque, can only check |type| and |size|. + assert_equals(blob.type, 'image/cat'); + assert_equals(blob.size, 2); + +}, 'exercises ImageCapture.takePhoto()'); + +image_capture_test(async t => { + let canvas = document.getElementById('canvas'); + let context = canvas.getContext('2d'); + context.fillStyle = 'red'; + context.fillRect(0, 0, 10, 10); + let stream = canvas.captureStream(); + + let capturer = new ImageCapture(stream.getVideoTracks()[0]); + let blob = await capturer.takePhoto(null); + + // JS Blob is almost-opaque, can only check |type| and |size|. + assert_equals(blob.type, 'image/cat'); + assert_equals(blob.size, 2); + +}, 'exercises ImageCapture.takePhoto(null)'); + +promise_test(t => { + let canvas = document.getElementById('canvas'); + let context = canvas.getContext('2d'); + context.fillStyle = 'red'; + context.fillRect(0, 0, 10, 10); + let stream = canvas.captureStream(); + let videoTrack = stream.getVideoTracks()[0]; + videoTrack.stop(); + + let capturer = new ImageCapture(videoTrack); + assert_equals(videoTrack.readyState, 'ended'); + + return promise_rejects_dom(t, 'InvalidStateError', capturer.takePhoto()) + +}, 'takePhoto() of an ended Track should throw "InvalidStateError"'); + +</script> |