[common][mediacapture][add new mediacapture-streams test cases] 08/294308/1
authortangkaiyuan <kaiyuan.tang@samsung.com>
Tue, 7 Mar 2023 07:36:11 +0000 (15:36 +0800)
committertangkaiyuan <kaiyuan.tang@samsung.com>
Tue, 7 Mar 2023 07:36:22 +0000 (15:36 +0800)
Change-Id: I6ca919cc0a3af7c188dc5d5475f9189a60904730
Signed-off-by: tangkaiyuan <kaiyuan.tang@samsung.com>
65 files changed:
common/tct-mediacapture-w3c-tests/config.xml
common/tct-mediacapture-w3c-tests/inst.wgt.py
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-api.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-empty-option-param.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-impossible-constraint.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-invalid-facing-mode.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-optional-constraint.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-required-constraint-with-ideal-value.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-trivial-constraint.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-unknownkey-option-param.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/META.yml [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-after-discard.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices-per-origin-ids.sub.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices-persistent-permission.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices-returned-objects.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-firstframe.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-add-audio-track.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-audio-only.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-clone.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-default-feature-policy.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-finished-add.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-gettrackid.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-id.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-idl.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-supported-by-feature-policy.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-video-only.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-audio-is-silence.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-video-is-black.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-end-manual.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-id.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-iframe-transfer.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-init.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-transfer-video.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-transfer.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/blank.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/crashtests/enumerateDevices-after-discard-1.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/enumerateDevices-with-navigation.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/idlharness.https.window.js [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/iframe-enumerate-cleared.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/iframe-enumerate.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/index.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/message-enumerateddevices.js [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/permission-helper.js [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/support/iframe-MediaStreamTrack-transfer-video.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/support/iframe-MediaStreamTrack-transfer.html [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/resources/featurepolicy.js [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/resources/get-host-info.sub.js [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/resources/testdriver-vendor.js [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/resources/testdriver.js [new file with mode: 0755]
common/tct-mediacapture-w3c-tests/resources/testharness.js
common/tct-mediacapture-w3c-tests/resources/testharnessreport.js
common/tct-mediacapture-w3c-tests/tests.full.xml
common/tct-mediacapture-w3c-tests/tests.xml

index 0a105c45fbae095d6f9a0f745d6e30104013932f..f14f6eb4d615d171dab27ea71513290faa2a279e 100755 (executable)
@@ -3,6 +3,14 @@
   <icon src="icon.png" height="117" width="117"/>
   <name>tct-mediacapture-w3c-tests</name>
   <tizen:application id="w3cmediaca.WebAPIW3CMediacaptureTests" package="w3cmediaca" required_version="8.0"/>
+  <tizen:privilege name="http://tizen.org/privilege/internet"/>
+  <tizen:privilege name="http://tizen.org/privilege/tv.inputdevice"/>
+  <tizen:privilege name="http://tizen.org/privilege/recorder"/>
+  <tizen:privilege name="http://tizen.org/privilege/mediacapture"/>
+  <tizen:privilege name="http://tizen.org/privilege/filesystem.read"/>
+  <tizen:privilege name="http://developer.samsung.com/privilege/camera"/>
+  <tizen:privilege name="http://developer.samsung.com/privilege/microphone"/>
+  <tizen:privilege name="http://developer.samsung.com/privilege/hostedapp_deviceapi_allow"/>
   <tizen:setting screen-orientation="landscape"/>
   <tizen:setting pointing-device-support="enable"/>
 </widget>
index 3bcf73639d0dfee35216b86bc89b730fd63d01fa..8e3380a448a4119999c5fe48189f941eee7fc11c 100755 (executable)
@@ -160,16 +160,15 @@ def instPKGs():
                         action_status = False
                         break
 
-    # Do some special copy/delete... steps
-    '''
-    (return_code, output) = doRemoteCMD(
-        "mkdir -p %s/tests" % PKG_SRC_DIR)
-    if return_code != 0:
-        action_status = False
-
-    if not doRemoteCopy("specname/tests", "%s/tests" % PKG_SRC_DIR):
-        action_status = False
-    '''
+    for item in glob.glob("%s/*" % SCRIPT_DIR):
+        if item.endswith(".wgt"):
+            continue
+        elif item.endswith("inst.py"):
+            continue
+        else:
+            item_name = os.path.basename(item)
+            if not doRemoteCopy(item, "%s/%s" % (PKG_SRC_DIR, item_name)):
+                action_status = False
 
     return action_status
 
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-api.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-api.https.html
new file mode 100755 (executable)
index 0000000..108cc42
--- /dev/null
@@ -0,0 +1,22 @@
+<!doctype html>
+<html>
+<head>
+<title>getUserMedia: test that getUserMedia is present</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#navigatorusermedia">
+<meta name='assert' content='Check that the getUserMedia() method is present.'/>
+</head>
+<body>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks for the presence of the
+<code>navigator.mediaDevices.getUserMedia</code> method.</p>
+<div id='log'></div>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script>
+test(function () {
+  assert_true(undefined !== navigator.mediaDevices && undefined !== navigator.mediaDevices.getUserMedia, "navigator.mediaDevices.getUserMedia exists");
+}, "mediaDevices.getUserMedia() is present on navigator");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-empty-option-param.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-empty-option-param.https.html
new file mode 100755 (executable)
index 0000000..571d58e
--- /dev/null
@@ -0,0 +1,34 @@
+<!doctype html>
+<html>
+<head>
+<title>getUserMedia({}) rejects with TypeError</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="https://w3c.github.io/mediacapture-main/#dom-mediadevices-getusermedia">
+</head>
+<body>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that getUserMedia with no value in the
+options parameter raises a TypeError exception.</p>
+
+<div id='log'></div>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script>
+promise_test(async () => {
+  try {
+    // Race a settled promise to check that the returned promise is already
+    // rejected.
+    await Promise.race([navigator.mediaDevices.getUserMedia({}),
+                       Promise.resolve()]);
+  } catch (error) {
+    assert_throws_js(TypeError, () => { throw error });
+    assert_false('constraintName' in error,
+                 "constraintName attribute not set as expected");
+    return;
+  }
+  assert_unreached("should have returned an already-rejected promise.");
+}, "Tests that getUserMedia is rejected with a TypeError when used with an empty options parameter");
+
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-impossible-constraint.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-impossible-constraint.https.html
new file mode 100755 (executable)
index 0000000..b187693
--- /dev/null
@@ -0,0 +1,37 @@
+<!doctype html>
+<html>
+<head>
+<title>Trivial mandatory constraint in getUserMedia</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-NavigatorUserMedia-getUserMedia-void-MediaStreamConstraints-constraints-NavigatorUserMediaSuccessCallback-successCallback-NavigatorUserMediaErrorCallback-errorCallback">
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#idl-def-NavigatorUserMediaError">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that setting an impossible mandatory
+constraint (width &gt;=1G) in getUserMedia works</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+promise_test(async () => {
+  await setMediaPermission("granted", ["camera"]);
+  // Note - integer conversion is weird for +inf and numbers > 2^32, so we
+  // use a number less than 2^32 for testing.
+  try {
+    await navigator.mediaDevices.getUserMedia({video: {width: {min:100000000}}});
+    assert_unreached("a Video stream of width 100M cannot be created");
+
+  } catch (error) {
+    assert_equals(error.name, "OverconstrainedError", "An impossible constraint triggers a OverconstrainedError");
+    assert_equals(error.constraint, "width", "The name of the not satisfied error is given in error.constraint");
+  }
+}, "Tests that setting an impossible constraint in getUserMedia fails");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-invalid-facing-mode.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-invalid-facing-mode.https.html
new file mode 100755 (executable)
index 0000000..9fadeb0
--- /dev/null
@@ -0,0 +1,31 @@
+<!doctype html>
+<html>
+<head>
+<title>Invalid facingMode in getUserMedia</title>
+<link rel="help" href="https://w3c.github.io/mediacapture-main/#def-constraint-facingMode">
+</head>
+<body>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that trying to set an empty facingMode
+  value in getUserMedia results in an OverconstrainedError.
+</p>
+
+<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="permission-helper.js"></script>
+<script>
+promise_test(async () => {
+  await setMediaPermission("granted", ["camera"]);
+  try {
+    await navigator.mediaDevices.getUserMedia({video: {facingMode: {exact: ''}}});
+    assert_unreached("The empty string is not a valid facingMode");
+  } catch (error) {
+    assert_equals(error.name, "OverconstrainedError");
+    assert_equals(error.constraint, "facingMode");
+  };
+}, "Tests that setting an invalid facingMode constraint in getUserMedia fails");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html
new file mode 100755 (executable)
index 0000000..cd937d4
--- /dev/null
@@ -0,0 +1,77 @@
+<!doctype html>
+<title>non-applicable constraint in getUserMedia</title>
+<link rel="author" title="Intel" href="http://www.intel.com"/>
+<link rel="help" href="https://w3c.github.io/mediacapture-main/#methods-5">
+<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="permission-helper.js"></script>
+
+<p class="instructions">When prompted, accept to share your audio and video stream.</p>
+
+<script>
+
+let video_only_valid_constraints = {
+  width: {min: 0},
+  height: {min: 0},
+  frameRate: {min: 0},
+  aspectRatio: {min: 0},
+  facingMode: {ideal: 'environment'},
+  resizeMode: {ideal: 'none'}
+}
+
+let video_only_invalid_constraints = {
+  width: {min: 100000000},
+  height: {min: 100000000},
+  frameRate: {min: 100000000},
+  aspectRatio: {min: 100000000},
+  facingMode: {exact: 'invalid'},
+  resizeMode: {exact: 'invalid'}
+}
+
+let audio_only_valid_constraints = {
+  volume: {min: 0},
+  sampleRate: {min: 0},
+  sampleSize: {min: 0},
+  echoCancellation: {ideal: true},
+  autoGainControl: {ideal: true},
+  noiseSuppression: {ideal: true},
+  latency: {min: 0},
+  channelCount: {min: 0}
+}
+
+let audio_only_invalid_constraints = {
+  volume: {min: 2},
+  sampleRate: {min: 100000000},
+  sampleSize: {min: 100000000},
+  echoCancellation: {exact: true},
+  autoGainControl: {exact: true},
+  noiseSuppression: {exact: true},
+  latency: {max: 0},
+  channelCount: {max: 0}
+}
+
+promise_test(async () => {
+  // Both permissions are needed at some point, asking for both at once
+  await setMediaPermission();
+  let stream = await navigator.mediaDevices.getUserMedia({audio: video_only_valid_constraints})
+  assert_equals(stream.getAudioTracks().length, 1, "the media stream has exactly one audio track");
+}, 'Test that setting video-only valid constraints inside of "audio" is simply ignored');
+
+promise_test(async () => {
+  let stream = await navigator.mediaDevices.getUserMedia({audio: video_only_invalid_constraints})
+  assert_equals(stream.getAudioTracks().length, 1, "the media stream has exactly one audio track");
+}, 'Test that setting video-only invalid constraints inside of "audio" is simply ignored');
+
+promise_test(async () => {
+  let stream = await navigator.mediaDevices.getUserMedia({video: audio_only_valid_constraints})
+  assert_equals(stream.getVideoTracks().length, 1, "the media stream has exactly one video track");
+}, 'Test that setting audio-only valid constraints inside of "video" is simply ignored');
+
+promise_test(async () => {
+  let stream = await navigator.mediaDevices.getUserMedia({video: audio_only_invalid_constraints})
+  assert_equals(stream.getVideoTracks().length, 1, "the media stream has exactly one video track");
+}, 'Test that setting audio-only invalid constraints inside of "video" is simply ignored');
+
+</script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-optional-constraint.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-optional-constraint.https.html
new file mode 100755 (executable)
index 0000000..c6bec6b
--- /dev/null
@@ -0,0 +1,32 @@
+<!doctype html>
+<html>
+<head>
+<title>Optional constraint recognized as optional in getUserMedia</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-NavigatorUserMedia-getUserMedia-void-MediaStreamConstraints-constraints-NavigatorUserMediaSuccessCallback-successCallback-NavigatorUserMediaErrorCallback-errorCallback">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that setting an optional constraint in
+getUserMedia is handled as optional</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+promise_test(async () => {
+  await setMediaPermission("granted", ["camera"]);
+  try {
+    const stream = await navigator.mediaDevices.getUserMedia({video: {advanced: [{width: {min:1024, max: 800}}]}});
+    assert_equals(stream.getVideoTracks().length, 1, "the media stream has exactly one video track");
+  } catch (error) {
+    assert_unreached("an optional constraint can't stop us from obtaining a video stream");
+  }
+}, "Tests that setting an optional constraint in getUserMedia is handled as optional");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-required-constraint-with-ideal-value.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-required-constraint-with-ideal-value.https.html
new file mode 100755 (executable)
index 0000000..ee74aa1
--- /dev/null
@@ -0,0 +1,33 @@
+<!doctype html>
+<html>
+<head>
+<title>Ideal value in required constraint in getUserMedia</title>
+<link rel="author" title="Intel" href="http://www.intel.com"/>
+<link rel="help" href="https://w3c.github.io/mediacapture-main/#dfn-fitness-distance">
+<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="permission-helper.js"></script>
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that setting a required constraint
+with an ideal value in getUserMedia works</p>
+<div id='log'></div>
+<script>
+promise_test(async t => {
+  await setMediaPermission("granted", ["camera"]);
+  const stream = await navigator.mediaDevices.getUserMedia({video: {width: {ideal: 320, min: 160}}});
+  assert_equals(stream.getVideoTracks().length, 1, "the media stream has exactly one video track");
+  assert_equals(stream.getVideoTracks()[0].getSettings().width, 320, 'ideal width is selected for getUserMedia() video tracks');
+  const video = document.createElement('video');
+  video.srcObject = stream;
+  await video.play();
+  assert_equals(video.videoWidth, 320, 'video width equals to track width');
+  stream.getVideoTracks()[0].stop();
+}, "Tests that setting a required constraint with an ideal value in getUserMedia works");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-trivial-constraint.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-trivial-constraint.https.html
new file mode 100755 (executable)
index 0000000..b69f5f5
--- /dev/null
@@ -0,0 +1,32 @@
+<!doctype html>
+<html>
+<head>
+<title>Trivial mandatory constraint in getUserMedia</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-NavigatorUserMedia-getUserMedia-void-MediaStreamConstraints-constraints-NavigatorUserMediaSuccessCallback-successCallback-NavigatorUserMediaErrorCallback-errorCallback">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that setting a trivial mandatory
+constraint (width &gt;=0) in getUserMedia works</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+promise_test(async () => {
+  await setMediaPermission();
+  try {
+    const stream = await navigator.mediaDevices.getUserMedia({video: {width: {min:0}}})
+    assert_equals(stream.getVideoTracks().length, 1, "the media stream has exactly one video track");
+  } catch (error) {
+    assert_unreached("a Video stream of minimally zero width can always be created");
+  }
+}, "Tests that setting a trivial mandatory constraint in getUserMedia works");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-unknownkey-option-param.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-unknownkey-option-param.https.html
new file mode 100755 (executable)
index 0000000..a895537
--- /dev/null
@@ -0,0 +1,31 @@
+<!doctype html>
+<html>
+<head>
+<title>getUserMedia({doesnotexist:true}) rejects with TypeError</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-NavigatorUserMedia-getUserMedia-void-MediaStreamConstraints-constraints-NavigatorUserMediaSuccessCallback-successCallback-NavigatorUserMediaErrorCallback-errorCallback">
+</head>
+<body>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that getUserMedia with an unknown value
+in the constraints parameter rejects with a TypeError.</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+promise_test(async () => {
+  try {
+    await navigator.mediaDevices.getUserMedia({doesnotexist:true})
+    assert_unreached("This should never be triggered since the constraints parameter only contains an unrecognized constraint");
+  } catch (error) {
+    assert_equals(error.name, "TypeError", "TypeError returned as expected");
+    assert_equals(error.constraintName, undefined, "constraintName attribute not set as expected");
+  }
+}, "Tests that getUserMedia is rejected with a TypeError when used with an unknown constraint");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/META.yml b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/META.yml
new file mode 100755 (executable)
index 0000000..97363cf
--- /dev/null
@@ -0,0 +1,5 @@
+spec: https://w3c.github.io/mediacapture-main/
+suggested_reviewers:
+  - alvestrand
+  - youennf
+  - jan-ivar
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-after-discard.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-after-discard.https.html
new file mode 100755 (executable)
index 0000000..e7ed9c9
--- /dev/null
@@ -0,0 +1,62 @@
+<!doctype html>
+<title>Test promises from MediaDevices methods in a discarded browsing
+  context</title>
+<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="permission-helper.js"></script>
+<body></body>
+<script>
+let devices;
+let child_DOMException;
+setup(() => {
+  const frame = document.createElement('iframe');
+  document.body.appendChild(frame);
+  devices = frame.contentWindow.navigator.mediaDevices;
+  child_DOMException = frame.contentWindow.DOMException;
+  frame.remove();
+});
+
+// https://w3c.github.io/mediacapture-main/#dom-mediadevices-getusermedia
+// If the current settings object's responsible document is NOT fully active,
+// return a promise rejected with a DOMException object whose name attribute
+// has the value "InvalidStateError".
+promise_test(async () => {
+  // `catch()` is used rather than static Promise methods because microtasks
+  // for `PromiseResolve()` do not run when Promises in inactive Documents are
+  // involved.  Whether microtasks for `catch()` run depends on the realm of
+  // the handler rather than the realm of the Promise.
+  // See https://github.com/whatwg/html/issues/5319.
+  let promise_already_rejected = false;
+  let rejected_reason;
+  devices.getUserMedia({audio:true}).catch(reason => {
+    promise_already_rejected = true;
+    rejected_reason = reason;
+  });
+  // Race a settled promise to check that the returned promise is already
+  // rejected.
+  await Promise.reject().catch(() => {
+    assert_true(promise_already_rejected,
+                'should have returned an already-rejected promise.');
+    assert_throws_dom('InvalidStateError', child_DOMException,
+                      () => { throw rejected_reason });
+  });
+}, 'getUserMedia() in a discarded browsing context');
+
+// https://w3c.github.io/mediacapture-main/#dom-mediadevices-enumeratedevices
+// https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-for-spec-authors
+// Promise resolution occurs only in parallel steps, so, if there is any
+// manipulation of the Promise, it would occur through a queued task.
+promise_test(() => {
+  let promise_is_pending = true;
+  // Don't use `finally()`, because it uses `PromiseResolve()` and so
+  // microtasks don't run.
+  devices.enumerateDevices().then(() => promise_is_pending = false,
+                                  () => promise_is_pending = false);
+  return Promise.resolve().then(() => {
+    assert_true(promise_is_pending,
+                'should have returned a pending promise.');
+  });
+}, 'enumerateDevices() in a discarded browsing context');
+</script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices-per-origin-ids.sub.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices-per-origin-ids.sub.https.html
new file mode 100755 (executable)
index 0000000..462415c
--- /dev/null
@@ -0,0 +1,87 @@
+<!doctype html>
+<html>
+<head>
+<title>enumerateDevices rotates deviceId across origins and after cookies get cleared</title>
+<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="permission-helper.js"></script>
+</head>
+<body>
+<iframe allow="camera 'src';microphone 'src'" id=same src="iframe-enumerate.html"></iframe>
+<iframe allow="camera 'src';microphone 'src'" id=cross src="https://www.w3c-test.org/mediacapture-streams/iframe-enumerate.html"></iframe>
+<script>
+
+  let deviceList;
+
+  promise_test(async t => {
+    await setMediaPermission();
+    const stream = await navigator.mediaDevices.getUserMedia({audio : true, video: true});
+    stream.getTracks().forEach(t => t.stop());
+    deviceList = await navigator.mediaDevices.enumerateDevices();
+    const msgWatcher = new EventWatcher(t, window, ['message']);
+    frames[0].postMessage('run', '*')
+    const e = await msgWatcher.wait_for('message');
+    const iframeDevices = e.data.devices;
+    assert_equals(deviceList.length, iframeDevices.length, "Same number of devices detected same-origin");
+    for (const device of deviceList) {
+      // Look for the same device in the iframe based on deviceId
+      // "default" can be used across several kinds, so it needs an additional check
+      // but we limit that check to "default" to detect re-use of deviceId across kinds
+      const sameDevice = iframeDevices.find(d => d.deviceId === device.deviceId && (device.deviceId !== "default" || d.kind === device.kind));
+      assert_true(!!sameDevice, "deviceIds stay the same when loaded in same origin");
+      assert_equals(sameDevice.label, device.label, "labels matches when deviceId matches");
+      assert_equals(sameDevice.kind, device.kind, "kind matches when deviceId matches");
+      // The group identifier MUST be uniquely generated for each document.
+      assert_not_equals(sameDevice.groupId, device.groupId, "groupId is specific to a document");
+    }
+    // setting a cookie as a way to detect if cookie clearing gets done
+    document.cookie = "test=true";
+    window.localStorage.touched = true;
+  }, "enumerateDevices has stable deviceIds across same-origin iframe");
+
+  promise_test(async t => {
+    const msgWatcher = new EventWatcher(t, window, ['message']);
+    frames[1].postMessage('run', '*')
+    const e = await msgWatcher.wait_for('message');
+    const iframeDevices = e.data.devices;
+    assert_equals(deviceList.length, iframeDevices.length, "Same number of devices detected cross-origin");
+    for (const device of deviceList) {
+      // An identifier can be reused across origins as long as
+      // it is not tied to the user and can be guessed by other means
+      // In practice, "default" is what is used today, so we hardcode it
+      // to be able to detect the general case of non-shared deviceIds
+      if (device.deviceId !== "default") {
+        const sameDevice = iframeDevices.find(d => d.deviceId === device.deviceId);
+        assert_false(!!sameDevice, "deviceIds are not shared across origin");
+      }
+      assert_false(!!iframeDevices.find(d => d.groupId === device.groupId), "groupId is specific to a document");
+    }
+  }, "enumerateDevices rotates deviceId across different-origin iframe");
+
+  promise_test(async t => {
+    const iframe = document.createElement("iframe");
+    iframe.setAttribute("allow", "camera 'src';microphone 'src'");
+    iframe.src = "iframe-enumerate-cleared.html";
+    document.body.appendChild(iframe);
+    const loadWatcher = new EventWatcher(t, iframe, ['load']);
+    await loadWatcher.wait_for('load');
+    assert_implements_optional(document.cookie === "", "Clear-Site-Data not enabled, can't test clearing deviceId");
+
+    const msgWatcher = new EventWatcher(t, window, ['message']);
+    frames[2].postMessage('run', '*')
+    const e = await msgWatcher.wait_for('message');
+    const iframeDevices = e.data.devices;
+    assert_equals(deviceList.length, iframeDevices.length, "Same number of devices detected after clearing cookies");
+    for (const device of deviceList) {
+      const sameDevice = iframeDevices.find(d => d.deviceId === device.deviceId);
+      assert_false(!!sameDevice, "deviceIds are not kept after clearing site data");
+      assert_false(!!iframeDevices.find(d => d.groupId === device.groupId), "groupId is specific to a document");
+    }
+
+  }, "enumerateDevices rotates deviceId after clearing site data");
+
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices-persistent-permission.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices-persistent-permission.https.html
new file mode 100755 (executable)
index 0000000..28081ba
--- /dev/null
@@ -0,0 +1,38 @@
+<!doctype html>
+<html>
+<head>
+<title>enumerateDevices depends only on capture state, not permission state</title>
+<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="permission-helper.js"></script>
+</head>
+<body>
+
+<script>
+  promise_test(async t => {
+    await setMediaPermission();
+    const stream = await navigator.mediaDevices.getUserMedia({audio : true, video: true});
+    stream.getTracks().forEach(t => t.stop());
+    // the page loaded below hasn't had capture enabled
+    // so enumerateDevices should not list detailed info yet
+    const iframe = document.createElement("iframe");
+    iframe.setAttribute("allow", "camera 'src';microphone 'src'");
+    iframe.src = "iframe-enumerate.html";
+    document.body.appendChild(iframe);
+    const loadWatcher = new EventWatcher(t, iframe, ['load']);
+    await loadWatcher.wait_for('load');
+    const msgWatcher = new EventWatcher(t, window, ['message']);
+    frames[0].postMessage('run', '*')
+    const e = await msgWatcher.wait_for('message');
+    const iframeDevices = e.data.devices;
+    const kinds = iframeDevices.map(({kind}) => kind);
+    assert_equals(kinds.length, new Set(kinds).size, "At most one of a kind prior to capture");
+    for (const device of iframeDevices) {
+      assert_equals(device.deviceId, "", "deviceId pre-capture is empty");
+      assert_equals(device.label, "", "label pre-capture is empty");
+      assert_equals(device.groupId, "", "groupId pre-capture is empty");
+    }
+  });
+</script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices-returned-objects.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices-returned-objects.https.html
new file mode 100755 (executable)
index 0000000..5ab0cff
--- /dev/null
@@ -0,0 +1,59 @@
+<!doctype html>
+<html>
+<head>
+<title>enumerateDevices is returning new MediaDeviceInfo objects every time</title>
+<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="permission-helper.js"></script>
+</head>
+<body>
+<script>
+function doTest(callGetUserMedia, testName)
+{
+    promise_test(async () => {
+        if (callGetUserMedia) {
+            await setMediaPermission();
+            await navigator.mediaDevices.getUserMedia({audio : true, video: true});
+        }
+
+        const deviceList1 =  await navigator.mediaDevices.enumerateDevices();
+        const deviceList2 =  await navigator.mediaDevices.enumerateDevices();
+
+        assert_equals(deviceList1.length, deviceList2.length);
+        for (let i = 0; i < deviceList1.length; i++) {
+            const device1 = deviceList1[i];
+            const device2 = deviceList2[i];
+            assert_not_equals(device1, device2);
+            assert_equals(device1.deviceId, device2.deviceId, "deviceId");
+            assert_equals(device1.kind, device2.kind, "kind");
+            if (!callGetUserMedia) {
+              /* For camera and microphone devices,
+               if the browsing context did not capture (i.e. getUserMedia() was not called or never resolved successfully),
+               the MediaDeviceInfo object will contain a valid value for kind
+               but empty strings for deviceId, label, and groupId. */
+              assert_equals(device1.deviceId, "", "deviceId is empty before capture");
+              assert_equals(device1.groupId, "", "groupId is empty before capture");
+              assert_equals(device1.label, "", "label is empty before capture");
+              assert_in_array(device1.kind, ["audioinput", "audiooutput", "videoinput", "kind is set to a valid value before capture"]);
+            }
+        }
+        /* Additionally, at most one device of each kind
+           will be listed in enumerateDevices() result. */
+        // FIXME: ensure browsers are tested as if they had multiple devices of at least one kind -
+        // this probably needs https://w3c.github.io/mediacapture-automation/ support
+        if (!callGetUserMedia) {
+            const deviceKinds = deviceList1.map(d => d.kind);
+            for (let kind of deviceKinds) {
+              assert_equals(deviceKinds.filter(x => x===kind).length, 1, "At most 1 " + kind + " prior to capture");
+            }
+        }
+    }, testName);
+}
+
+//doTest(false, "enumerateDevices returns expected mostly empty objects in case device-info permission is not granted");
+doTest(true, "enumerateDevices returns expected objects in case device-info permission is granted");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices.https.html
new file mode 100755 (executable)
index 0000000..13cc53d
--- /dev/null
@@ -0,0 +1,38 @@
+<!doctype html>
+<html>
+<head>
+<title>enumerateDevices: test that enumerateDevices is present</title>
+<link rel="author" title="Dr Alex Gouaillard" href="mailto:agouaillard@gmail.com"/>
+<link rel="help" href="https://w3c.github.io/mediacapture-main/#enumerating-devices">
+<meta name='assert' content='Check that the enumerateDevices() method is present.'/>
+</head>
+<body>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks for the presence of the
+<code>navigator.mediaDevices.enumerateDevices()</code> method.</p>
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+"use strict";
+
+//NOTE ALEX: for completion, a test for ondevicechange event is missing.
+
+promise_test(async () => {
+  const deviceList =  await navigator.mediaDevices.enumerateDevices();
+  for (const mediaInfo of deviceList) {
+    if (mediaInfo.kind == "audioinput" || mediaInfo.kind == "videoinput") {
+      assert_true(mediaInfo instanceof InputDeviceInfo);
+    } else if ( mediaInfo.kind == "audiooutput" ) {
+      assert_true(mediaInfo instanceof MediaDeviceInfo);
+    } else {
+      assert_unreached("mediaInfo.kind should be one of 'audioinput', 'videoinput', or 'audiooutput'.")
+    }
+  }
+}, "InputDeviceInfo is supported");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html
new file mode 100755 (executable)
index 0000000..94cf2b1
--- /dev/null
@@ -0,0 +1,48 @@
+<!doctype html>
+<html>
+<head>
+<title>Test navigator.mediaDevices.getSupportedConstraints()</title>
+<link rel="help" href="https://w3c.github.io/mediacapture-main/#enumerating-devices">
+<meta name='assert' content='Test the getSupportedConstraints() method.'/>
+</head>
+<body>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks for the presence of the
+<code>navigator.mediaDevices.getSupportedConstraints()</code> method.</p>
+<div id='log'></div>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script>
+"use strict";
+test(() => {
+  assert_inherits(navigator.mediaDevices, "getSupportedConstraints");
+  assert_equals(typeof navigator.mediaDevices.getSupportedConstraints, "function");
+}, "navigator.mediaDevices.getSupportedConstraints exists");
+
+{
+  const properties = [
+    "width",
+    "height",
+    "aspectRatio",
+    "frameRate",
+    "facingMode",
+    "resizeMode",
+    "sampleRate",
+    "sampleSize",
+    "echoCancellation",
+    "autoGainControl",
+    "noiseSuppression",
+    "latency",
+    "channelCount",
+    "deviceId",
+    "groupId"];
+  properties.forEach(property => {
+    test(()=>{
+      const supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
+      assert_true(supportedConstraints[property]);
+    }, property + " is supported");
+  });
+}
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html
new file mode 100755 (executable)
index 0000000..e8d8d2a
--- /dev/null
@@ -0,0 +1,126 @@
+<!doctype html>
+<html>
+<head>
+<title>getUserMedia: test that mediaDevices.getUserMedia is present</title>
+<link rel="author" title="Dr Alex Gouaillard" href="mailto:agouaillard@gmail.com"/>
+<link rel="help" href="https://w3c.github.io/mediacapture-main/#mediadevices-interface-extensions">
+<meta name='assert' content='Check that the mediaDevices.getUserMedia() method is present.'/>
+</head>
+<body>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks for the presence of the
+<code>navigator.mediaDevices.getUserMedia</code> method.</p>
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+test(function () {
+  assert_not_equals(navigator.mediaDevices.getUserMedia, undefined, "navigator.mediaDevices.getUserMedia exists.");
+  // TODO: do some stuff with it
+  assert_not_equals(navigator.mediaDevices.getSupportedConstraints, undefined, "navigator.mediaDevices.getSupportedConstraints exists.");
+  var list = navigator.mediaDevices.getSupportedConstraints();
+  // TODO: we are supposed to check that all values returned can be used in a constraint ....
+  // NOTE: the current list of attributes that may or may not be here
+  // ...   FF for example has many no tin that list, should we fail if an attribute is present but not listed in the specs?
+  //   list.width
+  //   list.height
+  //   list.aspectRatio
+  //   list.frameRate
+  //   list.facingMode
+  //   list.volume
+  //   list.sampleRate
+  //   list.sampleSize
+  //   list.echoCancellation
+  //   list.latency
+  //   list.channelCount
+  //   list.deviceId
+  //   list.groupId
+  }, "mediaDevices.getUserMedia() is present on navigator");
+
+promise_test(async t => {
+  // Both permissions are needed at some point, asking both at once
+  await setMediaPermission();
+  assert_true(navigator.mediaDevices.getSupportedConstraints()["groupId"],
+    "groupId should be supported");
+  const devices = await navigator.mediaDevices.enumerateDevices();
+  for (const device of devices) {
+    await navigator.mediaDevices.getUserMedia(
+        {video: {groupId: {exact: device.groupId}}}).then(stream => {
+      const found_device = devices.find(({deviceId}) =>
+        deviceId == stream.getTracks()[0].getSettings().deviceId);
+      assert_not_equals(found_device, undefined);
+      assert_equals(found_device.kind, "videoinput");
+      assert_equals(found_device.groupId, device.groupId);
+      stream.getTracks().forEach(t => t.stop());
+    }, error => {
+      assert_equals(error.name, "OverconstrainedError");
+      assert_equals(error.constraint, "groupId");
+      const found_device = devices.find(element =>
+        element.kind == "videoinput" && element.groupId == device.groupId);
+      assert_equals(found_device, undefined);
+    });
+  }
+}, 'groupId is correctly supported by getUserMedia() for video devices');
+
+promise_test(async t => {
+  assert_true(navigator.mediaDevices.getSupportedConstraints()["groupId"],
+    "groupId should be supported");
+  const devices = await navigator.mediaDevices.enumerateDevices();
+  for (const device of devices) {
+    await navigator.mediaDevices.getUserMedia(
+        {audio: {groupId: {exact: device.groupId}}}).then(stream => {
+      const found_device = devices.find(({deviceId}) =>
+        deviceId == stream.getTracks()[0].getSettings().deviceId);
+      assert_not_equals(found_device, undefined);
+      assert_equals(found_device.kind, "audioinput");
+      assert_equals(found_device.groupId, device.groupId);
+      stream.getTracks().forEach(t => t.stop());
+    }, error => {
+      assert_equals(error.name, "OverconstrainedError");
+      assert_equals(error.constraint, "groupId");
+      const found_device = devices.find(element =>
+        element.kind == "audioinput" && element.groupId == device.groupId);
+      assert_equals(found_device, undefined);
+    });
+  }
+}, 'groupId is correctly supported by getUserMedia() for audio devices');
+
+promise_test(async t => {
+  assert_true(navigator.mediaDevices.getSupportedConstraints()["resizeMode"],
+    "resizeMode should be supported");
+  const stream = await navigator.mediaDevices.getUserMedia(
+      { video: {resizeMode: {exact: 'none'}}});
+  const [track] = stream.getVideoTracks();
+  t.add_cleanup(() => track.stop());
+  assert_equals(track.getSettings().resizeMode, 'none');
+}, 'getUserMedia() supports setting none as resizeMode.');
+
+promise_test(async t => {
+  assert_true(navigator.mediaDevices.getSupportedConstraints()["resizeMode"],
+    "resizeMode should be supported");
+  const stream = await navigator.mediaDevices.getUserMedia(
+      { video: {resizeMode: {exact: 'crop-and-scale'}}});
+  const [track] = stream.getVideoTracks();
+  t.add_cleanup(() => track.stop());
+  assert_equals(track.getSettings().resizeMode, 'crop-and-scale');
+}, 'getUserMedia() supports setting crop-and-scale as resizeMode.');
+
+promise_test(async t => {
+  assert_true(navigator.mediaDevices.getSupportedConstraints()["resizeMode"],
+    "resizeMode should be supported");
+  try {
+    const stream = await navigator.mediaDevices.getUserMedia(
+        { video: {resizeMode: {exact: 'INVALID'}}});
+    t.add_cleanup(() => stream.getVideoTracks()[0].stop());
+    t.unreached_func('getUserMedia() should fail with invalid resizeMode')();
+  } catch (e) {
+    assert_equals(e.name, 'OverconstrainedError');
+    assert_equals(e.constraint, 'resizeMode');
+  }
+}, 'getUserMedia() fails with exact invalid resizeMode.');
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-firstframe.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-firstframe.https.html
new file mode 100755 (executable)
index 0000000..36b33da
--- /dev/null
@@ -0,0 +1,102 @@
+<!doctype html>
+<html>
+<head>
+<title>Assigning a MediaStream to a media element and not playing it results in rendering a first frame</title>
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that a HTMLMediaElement with an
+assigned MediaStream with a video track fires the appropriate events to reach
+the "canplay" event and readyState HAVE_ENOUGH_DATA even when not playing or
+autoplaying.</p>
+<video id="vid"></video>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script>
+'use strict';
+const vid = document.getElementById("vid");
+
+promise_test(async t => {
+  const wait = ms => new Promise(r => t.step_timeout(r, ms));
+  const timeout = (promise, time, msg) => Promise.race([
+    promise,
+    wait(time).then(() => Promise.reject(new Error(msg)))
+  ]);
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  t.add_cleanup(() => stream.getTracks().forEach(track => track.stop()));
+  vid.srcObject = stream;
+
+  await timeout(new Promise(r => vid.oncanplay = r), 8000, "canplay timeout");
+  assert_equals(vid.readyState, vid.HAVE_ENOUGH_DATA,
+    "readyState is HAVE_ENOUGH_DATA after \"canplay\"");
+}, "Tests that loading a MediaStream in a media element eventually results in \"canplay\" even when not playing or autoplaying");
+
+promise_test(async t => {
+  const wait = ms => new Promise(r => t.step_timeout(r, ms));
+  const timeout = (promise, time, msg) => Promise.race([
+    promise,
+    wait(time).then(() => Promise.reject(new Error(msg)))
+  ]);
+  const unexpected = e => assert_unreached(`Got unexpected event ${e.type}`);
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  t.add_cleanup(() => {
+    vid.ondurationchange = null;
+    stream.getTracks().forEach(track => track.stop())
+  });
+  vid.srcObject = stream;
+
+  vid.onloadstart = unexpected;
+  vid.ondurationchange = unexpected;
+  vid.onresize = unexpected;
+  vid.onloadedmetadata = unexpected;
+  vid.onloadeddata = unexpected;
+  vid.oncanplay = unexpected;
+  vid.oncanplaythrough = unexpected;
+
+  await timeout(new Promise(r => vid.onloadstart = r), 8000,
+    "loadstart timeout");
+  vid.onloadstart = unexpected;
+
+  await timeout(new Promise(r => vid.ondurationchange = r), 8000,
+    "durationchange timeout");
+  vid.ondurationchange = unexpected;
+  assert_equals(vid.duration, Infinity, "duration changes to Infinity");
+
+  await timeout(new Promise(r => vid.onresize = r), 8000,
+    "resize timeout");
+  vid.onresize = unexpected;
+  assert_not_equals(vid.videoWidth, 0,
+    "videoWidth is something after \"resize\"");
+  assert_not_equals(vid.videoHeight, 0,
+    "videoHeight is something after \"resize\"");
+
+  await timeout(new Promise(r => vid.onloadedmetadata = r), 8000,
+    "loadedmetadata timeout");
+  vid.onloadedmetadata = unexpected;
+  assert_greater_than_equal(vid.readyState, vid.HAVE_METADATA,
+    "readyState is at least HAVE_METADATA after \"loadedmetadata\"");
+
+  await timeout(new Promise(r => vid.onloadeddata = r), 8000,
+    "loadeddata timeout");
+  vid.onloadeddata = unexpected;
+  assert_equals(vid.readyState, vid.HAVE_ENOUGH_DATA,
+    "readyState is HAVE_ENOUGH_DATA after \"loadeddata\" since there's no buffering");
+
+  await timeout(new Promise(r => vid.oncanplay = r), 8000, "canplay timeout");
+  vid.oncanplay = unexpected;
+  assert_equals(vid.readyState, vid.HAVE_ENOUGH_DATA,
+    "readyState is HAVE_ENOUGH_DATA after \"canplay\" since there's no buffering");
+
+  await timeout(new Promise(r => vid.oncanplaythrough = r), 8000,
+    "canplaythrough timeout");
+  vid.oncanplaythrough = unexpected;
+  assert_equals(vid.readyState, vid.HAVE_ENOUGH_DATA,
+    "readyState is HAVE_ENOUGH_DATA after \"canplaythrough\"");
+
+  // Crank the event loop to see whether any more events are fired.
+  await wait(100);
+}, "Tests that loading a MediaStream in a media element sees all the expected (deterministic) events even when not playing or autoplaying");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html
new file mode 100755 (executable)
index 0000000..330d1f3
--- /dev/null
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Test that the HTMLMediaElement preload 'none' attribute value is ignored for MediaStream used as srcObject and MediaStream object URLs used as src.</title>
+        <link rel="author" title="Matthew Wolenetz" href="mailto:wolenetz@chromium.org"/>
+        <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="permission-helper.js"></script>
+    </head>
+    <body>
+        <p class="instructions">When prompted, accept to share your audio and video streams.</p>
+        <p class="instructions">This test checks that the HTMLMediaElement preload 'none' attribute value is ignored for MediaStream used as srcObject and MediaStream object URLs used as src.</p>
+        <div id=log></div>
+
+        <audio preload="none"></audio>
+        <video preload="none"></video>
+
+        <script>
+            async function testPreloadNone(mediaElement, stream)
+            {
+                let rejectSuspendedPromise, rejectErrorPromise, resolveDataLoadedPromise;
+                const suspended = new Promise((r, rej) => {
+                  rejectSuspendedPromise = rej;
+                });
+                const errored = new Promise((r, rej) => {
+                  rejectErrorPromise = rej;
+                });
+                const loaded = new Promise(resolve => {
+                  resolveDataLoadedPromise = resolve;
+                });
+
+                // The optional deferred load steps (for preload none) for MediaStream resources should be skipped.
+                mediaElement.addEventListener("suspend", () => {
+                  rejectSuspendedPromise("'suspend' should not be fired.")
+                });
+                mediaElement.addEventListener("error", () => {
+                  rejectErrorPromise("'error' should not be fired, code=" + mediaElement.error.code);
+                });
+
+                mediaElement.addEventListener("loadeddata", () => {
+                  assert_equals(mediaElement.networkState, mediaElement.NETWORK_LOADING);
+                  resolveDataLoadedPromise();
+                });
+
+                mediaElement.srcObject = stream;
+                assert_equals(mediaElement.networkState, mediaElement.NETWORK_NO_SOURCE); // Resource selection is active.
+                try {
+                  await Promise.race([suspended, errored, loaded]);
+                } catch (msg) {
+                  assert_unreached(msg);
+                }
+2            }
+
+            promise_test(async () =>
+            {
+                const aud = document.querySelector("audio");
+                // camera is needed for the next test, asking for both at once
+                await setMediaPermission();
+                let stream;
+                try {
+                  stream = await navigator.mediaDevices.getUserMedia({audio:true});
+                } catch (e) {
+                  assert_unreached("getUserMedia error callback was invoked.");
+                }
+                await testPreloadNone(aud, stream);
+            }, "Test that preload 'none' is ignored for MediaStream object URL used as srcObject for audio");
+
+            promise_test(async () =>
+            {
+                const vid = document.querySelector("video");
+                let stream;
+                try {
+                  stream = await navigator.mediaDevices.getUserMedia({video:true});
+                } catch (e) {
+                  assert_unreached("getUserMedia error callback was invoked.")
+                }
+                await testPreloadNone(vid, stream);
+
+            }, "Test that preload 'none' is ignored for MediaStream used as srcObject for video");
+        </script>
+    </body>
+</html>
+
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html
new file mode 100755 (executable)
index 0000000..8e34ccb
--- /dev/null
@@ -0,0 +1,238 @@
+<!doctype html>
+<html>
+<head>
+<meta name="timeout" content="long">
+<title>Assigning mediastream to a video element</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#navigatorusermedia">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that the MediaStream object returned by
+the success callback in getUserMedia can be properly assigned to a video element
+via the <code>srcObject</code> attribute.</p>
+
+<audio id="aud"></audio>
+<video id="vid"></video>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+'use strict';
+const vid = document.getElementById("vid");
+
+function queueTask(f) {
+  window.onmessage = f;
+  window.postMessage("hi");
+}
+
+promise_test(async t => {
+  await setMediaPermission();
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  t.add_cleanup(() => {
+    vid.srcObject = null;
+    stream.getTracks().forEach(track => track.stop());
+  });
+  vid.srcObject = stream;
+}, "Tests that a MediaStream can be assigned to a video element with srcObject");
+
+promise_test(async t => {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  t.add_cleanup(() => {
+    vid.srcObject = null;
+    stream.getTracks().forEach(track => track.stop());
+  });
+  vid.srcObject = stream;
+
+  assert_true(!vid.seeking, "A MediaStream is not seekable");
+  assert_equals(vid.seekable.length, 0, "A MediaStream is not seekable");
+}, "Tests that a MediaStream assigned to a video element is not seekable");
+
+promise_test(async t => {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  t.add_cleanup(() => {
+    vid.srcObject = null;
+    stream.getTracks().forEach(track => track.stop());
+  });
+  vid.srcObject = stream;
+
+  assert_equals(vid.readyState, vid.HAVE_NOTHING,
+    "readyState is HAVE_NOTHING initially");
+  await new Promise(r => vid.onloadeddata = r);
+  assert_equals(vid.readyState, vid.HAVE_ENOUGH_DATA,
+    "Upon having loaded a media stream, the UA sets readyState to HAVE_ENOUGH_DATA");
+}, "Tests that a MediaStream assigned to a video element is in readyState HAVE_NOTHING initially");
+
+promise_test(async t => {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  t.add_cleanup(() => {
+    vid.srcObject = null;
+    stream.getTracks().forEach(track => track.stop());
+  });
+
+  vid.preload = "metadata";
+  vid.srcObject = stream;
+
+  assert_equals(vid.buffered.length, 0,
+    "A MediaStream cannot be preloaded. Therefore, there are no buffered timeranges");
+  assert_equals(vid.preload, "none", "preload must always be none");
+  vid.preload = "auto";
+  assert_equals(vid.preload, "none", "Setting preload must be ignored");
+
+  await new Promise(r => vid.onloadeddata = r);
+  assert_equals(vid.buffered.length, 0,
+    "A MediaStream cannot be preloaded. Therefore, there are no buffered timeranges");
+
+  vid.srcObject = null;
+
+  assert_equals(vid.preload, "metadata",
+    "The preload attribute returns the value it had before using a MediaStream");
+}, "Tests that a video element with a MediaStream assigned is not preloaded");
+
+promise_test(async t => {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  t.add_cleanup(() => {
+    vid.srcObject = null;
+    stream.getTracks().forEach(track => track.stop());
+  });
+
+  vid.defaultPlaybackRate = 0.3;
+  vid.playbackRate = 0.3;
+  vid.onratechange = t.unreached_func("ratechange event must not be fired");
+  vid.srcObject = stream;
+
+  assert_equals(vid.defaultPlaybackRate, 1, "playback rate is always 1");
+  vid.defaultPlaybackRate = 0.5;
+  assert_equals(vid.defaultPlaybackRate, 1,
+    "Setting defaultPlaybackRate must be ignored");
+
+  assert_equals(vid.playbackRate, 1, "playback rate is always 1");
+  vid.playbackRate = 0.5;
+  assert_equals(vid.playbackRate, 1, "Setting playbackRate must be ignored");
+
+  vid.srcObject = null;
+  assert_equals(vid.defaultPlaybackRate, 0.3,
+    "The defaultPlaybackRate attribute returns the value it had before using a MediaStream");
+  assert_equals(vid.playbackRate, 0.3,
+    "The playbackRate attribute is set to the value of the defaultPlaybackRate attribute when unsetting srcObject");
+
+  // Check that there's no ratechange event
+  await new Promise(r => t.step_timeout(r, 100));
+}, "Tests that a video element with a MediaStream assigned ignores playbackRate attributes (defaultPlaybackRate is identical)");
+
+promise_test(async t => {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  t.add_cleanup(() => {
+    vid.srcObject = null;
+    stream.getTracks().forEach(track => track.stop());
+  });
+
+  vid.defaultPlaybackRate = 0.3;
+  vid.playbackRate = 0.4;
+  vid.onratechange = t.unreached_func("ratechange event must not be fired");
+  vid.srcObject = stream;
+
+  assert_equals(vid.defaultPlaybackRate, 1, "playback rate is always 1");
+  vid.defaultPlaybackRate = 0.5;
+  assert_equals(vid.defaultPlaybackRate, 1,
+    "Setting defaultPlaybackRate must be ignored");
+
+  assert_equals(vid.playbackRate, 1, "playback rate is always 1");
+  vid.playbackRate = 0.5;
+  assert_equals(vid.playbackRate, 1, "Setting playbackRate must be ignored");
+
+  vid.srcObject = null;
+  assert_equals(vid.defaultPlaybackRate, 0.3,
+    "The defaultPlaybackRate attribute returns the value it had before using a MediaStream");
+  assert_equals(vid.playbackRate, 0.3,
+    "The playbackRate attribute is set to the value of the defaultPlaybackRate attribute when unsetting srcObject (and fires ratechange)");
+  await new Promise(r => vid.onratechange = r);
+}, "Tests that a video element with a MediaStream assigned ignores playbackRate attributes (defaultPlaybackRate is different)");
+
+promise_test(async t => {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  t.add_cleanup(() => {
+    vid.srcObject = null;
+    stream.getTracks().forEach(track => track.stop());
+  });
+  vid.srcObject = stream;
+  await new Promise(r => vid.oncanplay = r);
+  vid.play();
+  await new Promise(r => vid.ontimeupdate = r);
+  assert_greater_than(vid.currentTime, 0,
+    "currentTime is greater than 0 after first timeupdate");
+
+  assert_equals(vid.played.length, 1,
+    "A MediaStream's timeline always consists of a single range");
+  assert_equals(vid.played.start(0), 0,
+    "A MediaStream's timeline always starts at zero");
+  assert_equals(vid.played.end(0), vid.currentTime,
+    "A MediaStream's end MUST return the last known currentTime");
+
+  const time = vid.currentTime;
+  vid.currentTime = 0;
+  assert_equals(vid.currentTime, time,
+    "The UA MUST ignore attempts to set the currentTime attribute");
+}, "Tests that a media element with an assigned MediaStream reports the played attribute as expected");
+
+promise_test(async t => {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  t.add_cleanup(() => {
+    vid.srcObject = null;
+    stream.getTracks().forEach(track => track.stop());
+  });
+  vid.srcObject = stream;
+
+  assert_equals(vid.currentTime, 0, "The initial value is 0");
+  vid.currentTime = 42;
+  assert_equals(vid.currentTime, 0,
+    "The UA MUST ignore attempts to set the currentTime attribute (default playback start position)");
+
+  await new Promise(r => vid.onloadeddata = r);
+  assert_equals(vid.currentTime, 0, "The initial value is 0");
+  vid.currentTime = 42;
+  assert_equals(vid.currentTime, 0,
+    "The UA MUST ignore attempts to set the currentTime attribute (official playback position)");
+
+  vid.play();
+  await new Promise(r => vid.ontimeupdate = r);
+  assert_greater_than(vid.currentTime, 0,
+    "currentTime is greater than 0 after first timeupdate");
+
+  const lastTime = vid.currentTime;
+  vid.currentTime = 0;
+  assert_equals(vid.currentTime, lastTime,
+    "The UA MUST ignore attempts to set the currentTime attribute (restart)");
+
+  for(const t of stream.getTracks()) {
+    t.stop();
+  }
+  await new Promise(r => vid.onended = r);
+  assert_greater_than_equal(vid.currentTime, lastTime,
+    "currentTime advanced after stopping");
+}, "Tests that a media element with an assigned MediaStream reports the currentTime attribute as expected");
+
+promise_test(async t => {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  t.add_cleanup(() => {
+    vid.srcObject = null;
+    stream.getTracks().forEach(track => track.stop());
+  });
+  vid.srcObject = stream;
+
+  await new Promise(r => t.step_timeout(r, 500));
+
+  vid.play();
+  await new Promise(r => vid.ontimeupdate = r);
+  assert_between_exclusive(vid.currentTime, 0, 0.5,
+    "currentTime starts at 0 and has progressed at first timeupdate");
+}, "Tests that a media element with an assigned MediaStream starts its timeline at 0 regardless of when the MediaStream was created");
+
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-add-audio-track.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-add-audio-track.https.html
new file mode 100755 (executable)
index 0000000..edbfaa4
--- /dev/null
@@ -0,0 +1,42 @@
+<!doctype html>
+<html>
+<head>
+<title>Adding a track to a MediaStream</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-MediaStreamTrackList-add-void-MediaStreamTrack-track">
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#event-mediastream-addtrack">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your audio stream, then your video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that adding a track to a MediaStream works as expected.</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+promise_test(async t => {
+  await setMediaPermission();
+  const audio = await navigator.mediaDevices.getUserMedia({audio: true});
+  const video = await navigator.mediaDevices.getUserMedia({video: true});
+  assert_equals(video.getAudioTracks().length, 0, "video mediastream starts with no audio track");
+  video.addTrack(audio.getAudioTracks()[0]);
+  assert_equals(video.getAudioTracks().length, 1, "video mediastream has now one audio track");
+  video.addTrack(audio.getAudioTracks()[0]);
+  // If track is already in stream's track set, then abort these steps.
+  assert_equals(video.getAudioTracks().length, 1, "video mediastream still has one audio track");
+
+  audio.onaddtrack = t.step_func(function () {
+    assert_unreached("onaddtrack is not fired when the script directly modified the track of a mediastream");
+  });
+
+  assert_equals(audio.getVideoTracks().length, 0, "audio mediastream starts with no video track");
+  audio.addTrack(video.getVideoTracks()[0]);
+  assert_equals(audio.getVideoTracks().length, 1, "audio mediastream now has one video track");
+}, "Tests that adding a track to a MediaStream works as expected");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-audio-only.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-audio-only.https.html
new file mode 100755 (executable)
index 0000000..4583074
--- /dev/null
@@ -0,0 +1,32 @@
+<!doctype html>
+<html>
+<head>
+<title>getUserMedia({audio:true}) creates a stream with at least an audio track</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-NavigatorUserMedia-getUserMedia-void-MediaStreamConstraints-constraints-NavigatorUserMediaSuccessCallback-successCallback-NavigatorUserMediaErrorCallback-errorCallback">
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-MediaStreamTrack-kind">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your audio stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that the MediaStream object returned by
+the success callback in getUserMedia has exactly one audio track.</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+promise_test(async () =>  {
+  await setMediaPermission();
+  const stream = await navigator.mediaDevices.getUserMedia({audio:true});
+  assert_true(stream instanceof MediaStream, "getUserMedia success callback comes with a MediaStream object");
+  assert_equals(stream.getAudioTracks().length, 1, "the media stream has exactly one audio track");
+  assert_equals(stream.getAudioTracks()[0].kind, "audio", "getAudioTracks() returns a sequence of tracks whose kind is 'audio'");
+  assert_equals(stream.getVideoTracks().length, 0, "the media stream has zero video track");
+}, "Tests that a MediaStream with exactly one audio track is returned");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-clone.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-clone.https.html
new file mode 100755 (executable)
index 0000000..7b6f890
--- /dev/null
@@ -0,0 +1,98 @@
+<!doctype html>
+<html>
+<head>
+<title>MediaStream and MediaStreamTrack clone()</title>
+<link rel="help" href="https://w3c.github.io/mediacapture-main/#dom-mediastream-clone">
+<link rel="help" href="https://w3c.github.io/mediacapture-main/#dom-mediastreamtrack-clone">
+</head>
+<body>
+<p class="instructions">When prompted, accept to give permission to use your audio and video devices.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that cloning MediaStreams and MediaStreamTracks works as expected.</p>
+<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="permission-helper.js"></script>
+<script>
+
+promise_test(async t => {
+  await setMediaPermission();
+  const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
+  assert_equals(stream.getAudioTracks().length, 1);
+  assert_equals(stream.getVideoTracks().length, 1);
+
+  const clone1 = stream.clone();
+  assert_equals(clone1.getAudioTracks().length, 1);
+  assert_equals(clone1.getVideoTracks().length, 1);
+  assert_not_equals(stream.getAudioTracks()[0].id, clone1.getAudioTracks()[0].id);
+  assert_not_equals(stream.getVideoTracks()[0].id, clone1.getVideoTracks()[0].id);
+
+  stream.getTracks().forEach(track => track.stop());
+  assert_false(stream.active);
+  assert_equals(stream.getAudioTracks()[0].readyState, "ended");
+  assert_equals(stream.getVideoTracks()[0].readyState, "ended");
+  assert_true(clone1.active);
+  assert_equals(clone1.getAudioTracks()[0].readyState, "live");
+  assert_equals(clone1.getVideoTracks()[0].readyState, "live");
+
+  clone1.getAudioTracks()[0].stop();
+  assert_true(clone1.active);
+  assert_equals(clone1.getAudioTracks()[0].readyState, "ended");
+  assert_equals(clone1.getVideoTracks()[0].readyState, "live");
+
+  const clone2 = clone1.clone();
+  assert_true(clone2.active);
+  assert_equals(clone2.getAudioTracks()[0].readyState, "ended");
+  assert_equals(clone2.getVideoTracks()[0].readyState, "live");
+
+  clone1.getVideoTracks()[0].stop();
+  clone2.getVideoTracks()[0].stop();
+
+  const clone3 = clone2.clone();
+  assert_false(clone3.active);
+  assert_equals(clone3.getAudioTracks()[0].readyState, "ended");
+  assert_equals(clone3.getVideoTracks()[0].readyState, "ended");
+  assert_not_equals(clone1.getAudioTracks()[0].id, clone2.getAudioTracks()[0].id);
+  assert_not_equals(clone1.getVideoTracks()[0].id, clone2.getVideoTracks()[0].id);
+  assert_not_equals(clone2.getAudioTracks()[0].id, clone3.getAudioTracks()[0].id);
+  assert_not_equals(clone2.getVideoTracks()[0].id, clone3.getVideoTracks()[0].id);
+  assert_not_equals(clone1.getAudioTracks()[0].id, clone3.getAudioTracks()[0].id);
+  assert_not_equals(clone1.getVideoTracks()[0].id, clone3.getVideoTracks()[0].id);
+}, "Tests that cloning MediaStream objects works as expected");
+
+promise_test(async t => {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
+  assert_equals(stream.getAudioTracks().length, 1);
+  assert_equals(stream.getVideoTracks().length, 1);
+  assert_equals(stream.getAudioTracks()[0].readyState, "live");
+  assert_equals(stream.getVideoTracks()[0].readyState, "live");
+  assert_true(stream.active);
+
+  const audio_clone = stream.getAudioTracks()[0].clone();
+  const video_clone = stream.getVideoTracks()[0].clone();
+  assert_equals(audio_clone.readyState, "live");
+  assert_equals(video_clone.readyState, "live");
+  assert_not_equals(stream.getAudioTracks()[0].id, audio_clone.id);
+  assert_not_equals(stream.getVideoTracks()[0].id, video_clone.id);
+
+  stream.getTracks().forEach(track => track.stop());
+  assert_false(stream.active);
+  assert_equals(stream.getAudioTracks()[0].readyState, "ended");
+  assert_equals(stream.getVideoTracks()[0].readyState, "ended");
+  assert_equals(audio_clone.readyState, "live");
+  assert_equals(video_clone.readyState, "live");
+
+  stream.addTrack(audio_clone);
+  stream.addTrack(video_clone);
+  assert_true(stream.active);
+
+  stream.getTracks().forEach(track => track.stop());
+  assert_false(stream.active);
+  assert_equals(audio_clone.readyState, "ended");
+  assert_equals(video_clone.readyState, "ended");
+}, "Tests that cloning MediaStreamTrack objects works as expected");
+
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-default-feature-policy.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-default-feature-policy.https.html
new file mode 100755 (executable)
index 0000000..9d1b316
--- /dev/null
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<meta name="timeout" content="long">
+<body>
+  <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="permission-helper.js"></script>
+  <script src="../../../resources/get-host-info.sub.js"></script>
+  <script src="../../../resources/featurepolicy.js"></script>
+  <script>
+  'use strict';
+
+  async function gUM({audio, video}) {
+    let stream;
+    if (!page_loaded_in_iframe()) {
+      await setMediaPermission();
+    }
+    try {
+      stream = await navigator.mediaDevices.getUserMedia({audio, video});
+      // getUserMedia must guarantee the number of tracks requested or fail.
+      if ((audio && stream.getAudioTracks().length == 0) ||
+          (video && stream.getVideoTracks().length == 0)) {
+        throw {name: `All requested devices must be present with ` +
+                     `audio ${audio} and video ${video}, or fail`};
+      }
+    } finally {
+      if (stream) {
+        stream.getTracks().forEach(track => track.stop());
+      }
+    }
+  }
+
+  async function must_disallow_gUM({audio, video}) {
+    try {
+      await gUM({audio, video});
+    } catch (e) {
+      if (e.name == 'NotAllowedError') {
+        return;
+      }
+      throw e;
+    }
+    throw {name: `audio ${audio} and video ${video} constraints must not be ` +
+                 `allowed.`};
+  }
+
+  const cross_domain = get_host_info().HTTP_REMOTE_ORIGIN;
+  run_all_fp_tests_allow_self(
+    cross_domain,
+    'microphone',
+    'NotAllowedError',
+    async () => {
+      await gUM({audio: true});
+      if (window.location.href.includes(cross_domain)) {
+        await must_disallow_gUM({video: true});
+        await must_disallow_gUM({audio: true, video: true});
+      }
+    }
+  );
+
+  run_all_fp_tests_allow_self(
+    cross_domain,
+    'camera',
+    'NotAllowedError',
+    async () => {
+      await gUM({video: true});
+      if (window.location.href.includes(cross_domain)) {
+        await must_disallow_gUM({audio: true});
+        await must_disallow_gUM({audio: true, video: true});
+      }
+    }
+  );
+
+  run_all_fp_tests_allow_self(
+    cross_domain,
+    'camera;microphone',
+    'NotAllowedError',
+    async () => {
+      await gUM({audio: true, video: true});
+      await gUM({audio: true});
+      await gUM({video: true});
+    }
+  );
+  </script>
+</body>
+
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-finished-add.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-finished-add.https.html
new file mode 100755 (executable)
index 0000000..9fd2f76
--- /dev/null
@@ -0,0 +1,35 @@
+<!doctype html>
+<html>
+<head>
+<title>Adding a track to an inactive MediaStream</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#widl-MediaStream-addTrack-void-MediaStreamTrack-track">
+<link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#widl-MediaStreamTrack-stop-void">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your audio stream, then
+your video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that adding a track to an inactive
+MediaStream is allowed.</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+promise_test(async () => {
+  await setMediaPermission();
+  const audio = await navigator.mediaDevices.getUserMedia({audio:true});
+  const video = await navigator.mediaDevices.getUserMedia({video:true});
+  audio.getAudioTracks()[0].stop();
+  assert_false(audio.active, "audio stream is inactive after stopping its only audio track");
+  assert_true(video.active, "video stream is active");
+  audio.addTrack(video.getVideoTracks()[0]);
+  audio.removeTrack(audio.getAudioTracks()[0]);
+}, "Tests that adding a track to an inactive MediaStream is allowed");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-gettrackid.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-gettrackid.https.html
new file mode 100755 (executable)
index 0000000..3abcc24
--- /dev/null
@@ -0,0 +1,29 @@
+<!doctype html>
+<html>
+<head>
+<title>Retrieving a track from a MediaStream</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-MediaStream-getTrackById-MediaStreamTrack-DOMString-trackId">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that MediaStream.getTrackById behaves as expected</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+promise_test(async () => {
+  await setMediaPermission("granted", ["camera"]);
+  const stream = await  navigator.mediaDevices.getUserMedia({video: true});
+  var track = stream.getVideoTracks()[0];
+  assert_equals(track, stream.getTrackById(track.id), "getTrackById returns track of given id");
+  assert_equals(stream.getTrackById(track.id + "foo"), null, "getTrackById of inexistant id  returns null");
+}, "Tests that MediaStream.getTrackById works as expected");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-id.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-id.https.html
new file mode 100755 (executable)
index 0000000..037a540
--- /dev/null
@@ -0,0 +1,31 @@
+<!doctype html>
+<html>
+<head>
+<title>getUserMedia() creates a stream with a proper id</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-MediaStream-id">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that the MediaStream object returned by
+the success callback in getUserMedia has a correct id.</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+
+const allowedCharacters = /^[\u0021\u0023-\u0027\u002A-\u002B\u002D-\u002E\u0030-\u0039\u0041-\u005A\u005E-\u007E]*$/;
+promise_test(async () => {
+  await setMediaPermission("granted", ["camera"]);
+  const stream = await navigator.mediaDevices.getUserMedia({video:true});
+  assert_equals(stream.id.length, 36, "the media stream id has 36 characters");
+  assert_regexp_match(stream.id, allowedCharacters, "the media stream id uses the set of allowed characters");
+}, "Tests that a MediaStream with a correct id is returned");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-idl.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-idl.https.html
new file mode 100755 (executable)
index 0000000..c6b7961
--- /dev/null
@@ -0,0 +1,77 @@
+<!doctype html>
+<html>
+<head>
+<title>MediaStream constructor algorithm</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#idl-def-MediaStream">
+<link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#widl-MediaStream-id">
+<link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#mediastream">
+<link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#event-mediastreamtrack-ended">
+<link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#widl-MediaStreamTrack-stop-void">
+<link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#widl-MediaStreamTrack-clone-MediaStreamTrack">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your video and audio stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that the MediaStream constructor
+follows the algorithm set in the spec.</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+  promise_test(async () => {
+  await setMediaPermission();
+  const stream = await navigator.mediaDevices.getUserMedia({video: true, audio:true})
+  let stream1 = new MediaStream();
+  assert_not_equals(stream.id, stream1.id, "Two different MediaStreams have different ids");
+  let stream2 = new MediaStream(stream);
+  assert_not_equals(stream.id, stream2.id, "A MediaStream constructed from another has a different id");
+  let audioTrack1 = stream.getAudioTracks()[0];
+  let videoTrack = stream.getVideoTracks()[0];
+  assert_equals(audioTrack1, stream2.getAudioTracks()[0], "A MediaStream constructed from another shares the same audio track");
+  assert_equals(videoTrack, stream2.getVideoTracks()[0], "A MediaStream constructed from another shares the same video track");
+  let stream4 = new MediaStream([audioTrack1]);
+  assert_equals(stream4.getTrackById(audioTrack1.id), audioTrack1, "a non-ended track gets added via the MediaStream constructor");
+
+  let audioTrack2 = audioTrack1.clone();
+  audioTrack2.addEventListener("ended", () => {
+    throw new Error("ended event should not be fired by MediaStreamTrack.stop().")
+  });
+  audioTrack2.stop();
+  assert_equals(audioTrack2.readyState, "ended", "a stopped track is marked ended synchronously");
+
+      let stream3 = new MediaStream([audioTrack2, videoTrack]);
+      assert_equals(stream3.getTrackById(audioTrack2.id), audioTrack2, "an ended track gets added via the MediaStream constructor");
+      assert_equals(stream3.getTrackById(videoTrack.id), videoTrack, "a non-ended track gets added via the MediaStream constructor even if the previous track was ended");
+
+      let stream5 = new MediaStream([audioTrack2]);
+      assert_equals(stream5.getTrackById(audioTrack2.id), audioTrack2, "an ended track gets added via the MediaStream constructor");
+      assert_false(stream5.active, "a MediaStream created using the MediaStream() constructor whose arguments are lists of MediaStreamTrack objects that are all ended, the MediaStream object MUST be created with its active attribute set to false");
+
+      audioTrack1.stop();
+      assert_equals(audioTrack1.readyState, "ended",
+        "Stopping audioTrack1 marks it ended synchronously");
+
+      videoTrack.stop();
+      assert_equals(videoTrack.readyState, "ended",
+        "Stopping videoTrack marks it ended synchronously");
+
+      assert_false(stream.active,
+        "The original MediaStream is marked inactive synchronously");
+      assert_false(stream1.active,
+        "MediaStream 1 is marked inactive synchronously");
+      assert_false(stream2.active,
+        "MediaStream 2 is marked inactive synchronously");
+      assert_false(stream3.active,
+        "MediaStream 3 is marked inactive synchronously");
+      assert_false(stream4.active,
+        "MediaStream 4 is marked inactive synchronously");
+
+}, "Tests that a MediaStream constructor follows the algorithm set in the spec");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html
new file mode 100755 (executable)
index 0000000..a00e5bd
--- /dev/null
@@ -0,0 +1,140 @@
+<!doctype html>
+<html>
+<head>
+<title>Removing a track from a MediaStream</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-MediaStreamTrackList-remove-void-MediaStreamTrack-track">
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#event-mediastream-removetrack">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your audio stream, then your video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that removinging a track from a MediaStream works as expected.</p>
+<video id="video" height="120" width="160" autoplay muted></video>
+<audio id="audio" autoplay muted></audio>
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+
+promise_test(async t => {
+  await setMediaPermission();
+  const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
+  const tracks = stream.getTracks();
+  t.add_cleanup(() => tracks.forEach(track => track.stop()));
+  const stream2 = await navigator.mediaDevices.getUserMedia({audio: true});
+  tracks.push(...stream2.getTracks());
+
+  stream.onremovetrack = stream2.onremovetrack = t.step_func(() =>
+    assert_unreached("onremovetrack is not triggered by script itself"));
+
+  assert_equals(stream.getTracks().length, 2, "mediastream starts with 2 tracks");
+  stream.removeTrack(stream.getVideoTracks()[0]);
+  assert_equals(stream.getTracks().length, 1, "mediastream has 1 track left");
+  stream.removeTrack(stream.getAudioTracks()[0]);
+  assert_equals(stream.getTracks().length, 0, "mediastream has no tracks left");
+  stream.removeTrack(stream2.getTracks()[0]); // should not throw
+
+  // Allow time to verify no events fire.
+  await new Promise(r => t.step_timeout(r, 1));
+
+}, "Tests that a removal from a MediaStream works as expected");
+
+async function doesEventFire(t, target, name, ms = 1) {
+  const cookie = {};
+  const value = await Promise.race([
+    new Promise(r => target.addEventListener(name, r, {once: true})),
+    new Promise(r => t.step_timeout(r, ms)).then(() => cookie)
+  ]);
+  return value !== cookie;
+}
+
+const doEventsFire = (t, target1, target2, name, ms = 1) => Promise.all([
+  doesEventFire(t, target1, "ended", ms),
+  doesEventFire(t, target2, "ended", ms)
+]);
+
+promise_test(async t => {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
+  const tracks = stream.getTracks();
+
+  audio.srcObject = video.srcObject = stream;
+
+  t.add_cleanup(() => {
+    for (const track of tracks) {
+      track.stop();
+    }
+    audio.srcObject = video.srcObject = null;
+  });
+
+  await Promise.all([
+    new Promise(r => audio.onloadedmetadata = r),
+    new Promise(r => video.onloadedmetadata = r)
+  ]);
+
+  assert_equals(audio.ended, false, "audio element starts out not ended");
+  assert_equals(video.ended, false, "video element starts out not ended");
+
+  stream.removeTrack(stream.getVideoTracks()[0]);
+  {
+    const [audioDidEnd, videoDidEnd] = await doEventsFire(t, audio, video, "ended");
+    assert_equals(audio.ended, false, "audio element unaffected");
+    assert_equals(audioDidEnd, false, "no audio ended event should fire yet");
+    assert_equals(video.ended, false, "video element keeps going with audio track");
+    assert_equals(videoDidEnd, false, "no video ended event should fire yet");
+  }
+  stream.removeTrack(stream.getAudioTracks()[0]);
+  {
+    const [audioDidEnd, videoDidEnd] = await doEventsFire(t, audio, video, "ended");
+    assert_equals(audio.ended, true, "audio element ended because no more audio tracks");
+    assert_equals(audioDidEnd, true, "go audio ended event");
+    assert_equals(video.ended, true, "video element ended because no more tracks");
+    assert_equals(videoDidEnd, true, "got video ended event");
+  }
+}, "Test that removal from a MediaStream fires ended on media elements (video first)");
+
+promise_test(async t => {
+  const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true});
+  const tracks = stream.getTracks();
+
+  audio.srcObject = video.srcObject = stream;
+
+  t.add_cleanup(() => {
+    for (const track of tracks) {
+      track.stop();
+    }
+    audio.srcObject = video.srcObject = null;
+  });
+
+  await Promise.all([
+    new Promise(r => audio.onloadedmetadata = r),
+    new Promise(r => video.onloadedmetadata = r)
+  ]);
+
+  assert_equals(audio.ended, false, "audio element starts out not ended");
+  assert_equals(video.ended, false, "video element starts out not ended");
+
+  stream.removeTrack(stream.getAudioTracks()[0]);
+  {
+    const [audioDidEnd, videoDidEnd] = await doEventsFire(t, audio, video, "ended");
+    assert_equals(audio.ended, true, "audio element ended because no more audio tracks");
+    assert_equals(audioDidEnd, true, "got audio ended event");
+    assert_equals(video.ended, false, "video element keeps going with video track");
+    assert_equals(videoDidEnd, false, "no video ended event should fire yet");
+  }
+  stream.removeTrack(stream.getVideoTracks()[0]);
+  {
+    const [audioDidEnd, videoDidEnd] = await doEventsFire(t, audio, video, "ended");
+    assert_equals(audio.ended, true, "audio element remains ended from before");
+    assert_equals(audioDidEnd, false, "no second audio ended event should fire");
+    assert_equals(video.ended, true, "video element ended because no more tracks");
+    assert_equals(videoDidEnd, true, "got video ended event");
+  }
+}, "Test that removal from a MediaStream fires ended on media elements (audio first)");
+
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-supported-by-feature-policy.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-supported-by-feature-policy.html
new file mode 100755 (executable)
index 0000000..ac18604
--- /dev/null
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>Test that camera and microphone are advertised in the feature list</title>
+<link rel="help" href="https://w3c.github.io/webappsec-feature-policy/#dom-featurepolicy-features">
+<link rel="help" href="https://w3c.github.io/mediacapture-main/#feature-policy-integration">
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script>
+test(() => {
+    assert_in_array('camera', document.featurePolicy.features());
+}, 'document.featurePolicy.features should advertise camera.');
+
+test(() => {
+    assert_in_array('microphone', document.featurePolicy.features());
+}, 'document.featurePolicy.features should advertise microphone.');
+</script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-video-only.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-video-only.https.html
new file mode 100755 (executable)
index 0000000..9c8c867
--- /dev/null
@@ -0,0 +1,32 @@
+<!doctype html>
+<html>
+<head>
+<title>getUserMedia({video:true}) creates a stream with one video track</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-NavigatorUserMedia-getUserMedia-void-MediaStreamConstraints-constraints-NavigatorUserMediaSuccessCallback-successCallback-NavigatorUserMediaErrorCallback-errorCallback">
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-MediaStreamTrack-kind">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that the MediaStream object returned by
+the success callback in getUserMedia has exactly one video track and no audio.</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+promise_test(async () =>  {
+  await setMediaPermission("granted", ["camera"]);
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  assert_true(stream instanceof MediaStream, "getUserMedia success callback comes with a MediaStream object");
+  assert_equals(stream.getAudioTracks().length, 0, "the media stream has zero audio track");
+  assert_equals(stream.getVideoTracks().length, 1, "the media stream has exactly one video track");
+  assert_equals(stream.getVideoTracks()[0].kind, "video", "getAudioTracks() returns a sequence of tracks whose kind is 'video'");
+}, "Tests that a MediaStream with at least one video track is returned");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-audio-is-silence.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-audio-is-silence.https.html
new file mode 100755 (executable)
index 0000000..5162be3
--- /dev/null
@@ -0,0 +1,59 @@
+<!doctype html>
+<html>
+<head>
+<title>A disabled audio track is rendered as silence</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#introduction">
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#mediastreams-as-media-elements">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your audio stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that a disabled audio track in a
+MediaStream is rendered as silence. It relies on the
+<a href="https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html">
+Web Audio API</a>.</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+const aud = document.getElementById("aud");
+promise_test(async t => {
+  await setMediaPermission("granted", ["microphone"]);
+  const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+  var ctx = new AudioContext();
+  var streamSource = ctx.createMediaStreamSource(stream);
+  var silenceDetector = ctx.createScriptProcessor(1024);
+  var count = 10;
+  let resolveAudioProcessPromise;
+  const audioProcessed = new Promise(res => resolveAudioProcessPromise = res)
+
+  silenceDetector.onaudioprocess = function (e) {
+    var buffer1 = e.inputBuffer.getChannelData(0);
+    var buffer2 = e.inputBuffer.getChannelData(1);
+    var out = e.outputBuffer.getChannelData(0);
+    out = new Float32Array(buffer1);
+    for (var i = 0; i < buffer1.length; i++) {
+      assert_equals(buffer1[i], 0, "Audio buffer entry #" + i + " in channel 0 is silent");
+    }
+    for (var i = 0; i < buffer2.length; i++) {
+      assert_equals(buffer2[i], 0, "Audio buffer entry #" + i + " in channel 1 is silent");
+    }
+    count--;
+    if (count === 0) {
+      silenceDetector.onaudioprocess = null;
+      resolveAudioProcessPromise();
+    }
+  };
+  stream.getAudioTracks()[0].enabled = false;
+
+  streamSource.connect(silenceDetector);
+  silenceDetector.connect(ctx.destination);
+}, "Tests that a disabled audio track in a MediaStream is rendered as silence");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-video-is-black.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-video-is-black.https.html
new file mode 100755 (executable)
index 0000000..61ca647
--- /dev/null
@@ -0,0 +1,56 @@
+<!doctype html>
+<html>
+<head>
+<title>A disabled video track is rendered as blackness</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#introduction">
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#mediastreams-as-media-elements">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that a disabled video track in a
+MediaStream is rendered as blackness.</p>
+<video id="vid"></video>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+const vid = document.getElementById("vid");
+const cv = document.createElement("canvas");
+promise_test(async () => {
+  await setMediaPermission("granted", ["camera"]);
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  if (stream.getVideoTracks()[0].enabled) {
+    stream.getVideoTracks()[0].enabled = false;
+  }
+
+  let resolveLoadedPromise;
+  const videoLoaded = new Promise(res => resolveLoadedPromise = res)
+  var testOnceLoadeddata = function() {
+        vid.removeEventListener("loadeddata", testOnceLoadeddata, false);
+        cv.width = vid.offsetWidth;
+        cv.height = vid.offsetHeight;
+        var ctx = cv.getContext("2d");
+        ctx.drawImage(vid,0,0);
+        var imageData = ctx.getImageData(0, 0, cv.width, cv.height);
+        for (var i = 0; i < imageData.data.length; i+=4) {
+          assert_equals(imageData.data[i], 0, "No red component in pixel #" + i);
+          assert_equals(imageData.data[i + 1], 0, "No green component in pixel #" + i);
+          assert_equals(imageData.data[i + 2], 0, "No blue component in pixel #" + i);
+          assert_equals(imageData.data[i + 3], 255, "No transparency in pixel #" + i);
+        }
+        resolveLoadedPromise();
+  };
+
+  vid.srcObject = stream;
+  vid.play();
+  vid.addEventListener("loadeddata", testOnceLoadeddata, false);
+}, "Tests that a disabled video track in a MediaStream is rendered as blackness");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html
new file mode 100755 (executable)
index 0000000..4464aae
--- /dev/null
@@ -0,0 +1,83 @@
+<!doctype html>
+<title>MediaStreamTrack applyConstraints</title>
+<p class="instructions">When prompted, accept to share your video stream.</p>
+<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="permission-helper.js"></script>
+<script>
+  'use strict'
+
+  // https://w3c.github.io/mediacapture-main/#dom-mediastreamtrack-applyconstraints
+
+  promise_test(async t => {
+    await setMediaPermission("granted", ["camera"]);
+    return navigator.mediaDevices.getUserMedia({ video: true })
+      .then(t.step_func(stream => {
+        return stream.getVideoTracks()[0].applyConstraints(
+          { groupId: { exact: "INVALID" } }).then(
+            t.unreached_func('Accepted invalid groupID'),
+            t.step_func(e => {
+              assert_equals(e.name, 'OverconstrainedError');
+              assert_equals(e.constraint, 'groupId');
+            }));
+      }));
+  }, 'applyConstraints rejects invalid groupID');
+
+  promise_test(t => {
+    return navigator.mediaDevices.getUserMedia({ video: true })
+      .then(t.step_func(stream => {
+        var track = stream.getVideoTracks()[0];
+        var groupId = track.getSettings().groupId;
+        return track.applyConstraints({ groupId: "INVALID" }).then(
+          t.step_func(() => {
+            assert_equals(track.getSettings().groupId, groupId);
+          }));
+      }));
+  }, 'applyConstraints accepts invalid ideal groupID, does not change setting');
+
+  promise_test(t => {
+    return navigator.mediaDevices.getUserMedia({ video: true })
+      .then(t.step_func(stream => {
+        var track = stream.getVideoTracks()[0];
+        var groupId = track.getSettings().groupId;
+        return navigator.mediaDevices.enumerateDevices().then(devices => {
+          var anotherDevice = devices.find(device => {
+            return device.kind == "videoinput" && device.groupId != groupId;
+          });
+          if (anotherDevice !== undefined) {
+            return track.applyConstraints(
+              { groupId: { exact: anotherDevice.groupId } }).then(
+                t.unreached_func(),
+                t.step_func(e => {
+                  assert_equals(e.name, 'OverconstrainedError');
+                  assert_equals(e.constraint, 'groupId');
+                }));
+          }
+        });
+      }));
+  }, 'applyConstraints rejects attempt to switch device using groupId');
+
+  promise_test(async t => {
+    const stream = await navigator.mediaDevices.getUserMedia({ video: true });
+    const [track] = stream.getVideoTracks();
+    t.add_cleanup(() => track.stop());
+    try {
+      await track.applyConstraints({ resizeMode: { exact: "INVALID" } });
+      t.unreached_func('applyConstraints() must fail with invalid resizeMode')();
+    } catch (e) {
+      assert_equals(e.name, 'OverconstrainedError');
+      assert_equals(e.constraint, 'resizeMode');
+    }
+  }, 'applyConstraints rejects invalid resizeMode');
+
+  promise_test(async t => {
+    const stream = await navigator.mediaDevices.getUserMedia({ video: true });
+    const [track] = stream.getVideoTracks();
+    t.add_cleanup(() => track.stop());
+    const resizeMode = track.getSettings().resizeMode;
+    await track.applyConstraints({ resizeMode: "INVALID" });
+    assert_equals(track.getSettings().resizeMode, resizeMode);
+  }, 'applyConstraints accepts invalid ideal resizeMode, does not change setting');
+</script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-end-manual.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-end-manual.https.html
new file mode 100755 (executable)
index 0000000..f73e72a
--- /dev/null
@@ -0,0 +1,54 @@
+<!doctype html>
+<html>
+<head>
+<title>Test that mediastreamtrack are properly ended</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://w3c.github.io/mediacapture-main/getusermedia.html#mediastreamtrack">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your video and audio
+stream, and then revoke that permission.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that the video and audio tracks of
+MediaStream object returned by the success callback in getUserMedia are
+correctly set into inactive state when permission is revoked.</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+'use strict';
+promise_test(async t => {
+  await setMediaPermission();
+  const stream = await navigator.mediaDevices.getUserMedia({
+    audio: true,
+    video: true,
+  });
+
+  const vidTrack = stream.getVideoTracks()[0];
+  assert_equals(vidTrack.readyState, "live",
+    "The video track object is in live state");
+  const vidEnded = new Promise(r => vidTrack.onended = r);
+  const audTrack = stream.getAudioTracks()[0];
+  assert_equals(audTrack.readyState, "live",
+    "The audio track object is in live state");
+  const audEnded = new Promise(r => audTrack.onended = r);
+
+  await Promise.race([vidEnded, audEnded]);
+  assert_equals(stream.getTracks().filter(t => t.readyState == "ended").length,
+    1, "Only one track is ended after first track's ended event");
+  assert_equals(stream.getTracks().filter(t => t.readyState == "live").length,
+    1, "One track is still live after first track's ended event");
+  assert_true(stream.active, "MediaStream is still active");
+
+  await Promise.all([vidEnded, audEnded]);
+  assert_equals(vidTrack.readyState, "ended", "Video track ended as expected");
+  assert_equals(audTrack.readyState, "ended", "Audio track ended as expected");
+  assert_false(stream.active, "MediaStream has become inactive as expected");
+}, "Tests that MediaStreamTracks end properly on permission revocation");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html
new file mode 100755 (executable)
index 0000000..c07eaff
--- /dev/null
@@ -0,0 +1,154 @@
+<!doctype html>
+<title>MediaStreamTrack and InputDeviceInfo GetCapabilities</title>
+<meta name="timeout" content="long">
+<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="permission-helper.js"></script>
+<script>
+
+const audioProperties = [
+  {name: "sampleRate", type: "number"},
+  {name: "sampleSize", type: "number"},
+  {name: "echoCancellation", type: "boolean"},
+  {name: "autoGainControl", type: "boolean"},
+  {name: "noiseSuppression", type: "boolean"},
+  {name: "latency", type: "number"},
+  {name: "channelCount", type: "number"},
+  {name: "deviceId", type: "string"},
+  {name: "groupId", type: "string"}
+];
+
+const videoProperties = [
+  {name: "width", type: "number"},
+  {name: "height", type: "number"},
+  {name: "aspectRatio", type: "number"},
+  {name: "frameRate", type: "number"},
+  {name: "facingMode", type: "enum-any", validValues: ["user", "environment", "left", "right"]},
+  {name: "resizeMode", type: "enum-all", validValues: ["none", "crop-and-scale"]},
+  {name: "deviceId", type: "string"},
+  {name: "groupId", type: "string"},
+];
+
+function verifyBooleanCapability(capability) {
+  assert_less_than_equal(capability.length, 2);
+  capability.forEach(c => assert_equals(typeof c, "boolean"));
+}
+
+function verifyNumberCapability(capability) {
+    assert_equals(typeof capability, "object");
+    assert_equals(Object.keys(capability).length, 2);
+    assert_true(capability.hasOwnProperty('min'));
+    assert_true(capability.hasOwnProperty('max'));
+    assert_less_than_equal(capability.min, capability.max);
+}
+
+// Verify that any value provided by an enum capability is in the set of valid
+// values.
+function verifyEnumAnyCapability(capability, enumMembers) {
+  capability.forEach(c => {
+    assert_equals(typeof c, "string");
+    assert_in_array(c, enumMembers);
+  });
+}
+
+// Verify that all required values are supported by a capability.
+function verifyEnumAllCapability(capability, enumMembers, testNamePrefix) {
+  enumMembers.forEach(member => {
+    test(() => {
+      assert_in_array(member, capability);
+    }, testNamePrefix + " Value: " + member);
+  });
+}
+
+function testCapabilities(capabilities, property, testNamePrefix) {
+  let testName = testNamePrefix + " " + property.name;
+  test(() => {
+    assert_true(capabilities.hasOwnProperty(property.name));
+  }, testName + " property present.");
+
+  const capability = capabilities[property.name];
+  testName += " properly supported.";
+  if (property.type == "string") {
+    test(() => {
+      assert_equals(typeof capability, "string");
+    }, testName);
+  }
+
+  if (property.type == "boolean") {
+    test(() => {
+      verifyBooleanCapability(capability);
+    }, testName);
+  }
+
+  if (property.type == "number") {
+    test(() => {
+      verifyNumberCapability(capability);
+    }, testName);
+  }
+
+  if (property.type.startsWith("enum")) {
+    test(() => {
+      verifyEnumAnyCapability(capability, property.validValues);
+    }, testName);
+
+    if (property.type == "enum-all") {
+      verifyEnumAllCapability(capability, property.validValues, testName);
+    }
+  }
+}
+
+{
+  audioProperties.forEach((property, i) => {
+    promise_test(async t => {
+      if (i === 0) await setMediaPermission("granted", ["microphone"]);
+      const stream = await navigator.mediaDevices.getUserMedia({audio: true});
+      t.add_cleanup(() => stream.getAudioTracks()[0].stop());
+      const audioCapabilities = stream.getAudioTracks()[0].getCapabilities();
+      testCapabilities(audioCapabilities, property, "Audio track getCapabilities()");
+    }, "Setup audio MediaStreamTrack getCapabilities() test for " + property.name);
+  });
+
+  videoProperties.forEach(property => {
+    promise_test(async t => {
+      const stream = await navigator.mediaDevices.getUserMedia({video: true});
+      t.add_cleanup(() => stream.getVideoTracks()[0].stop());
+      const audioCapabilities = stream.getVideoTracks()[0].getCapabilities();
+      testCapabilities(audioCapabilities, property, "Video track getCapabilities()");
+    }, "Setup video MediaStreamTrack getCapabilities() test for " + property.name);
+  });
+}
+
+{
+  audioProperties.forEach(property => {
+    promise_test(async t => {
+      const devices = await navigator.mediaDevices.enumerateDevices();
+      for (const device of devices) {
+        // Test only one device.
+        if (device.kind == "audioinput") {
+          assert_inherits(device, "getCapabilities");
+          const capabilities = device.getCapabilities();
+          testCapabilities(capabilities, property, "Audio device getCapabilities()");
+          break;
+        }
+      }
+    }, "Setup audio InputDeviceInfo getCapabilities() test for " + property.name);
+  });
+
+  videoProperties.forEach(property => {
+    promise_test(async t => {
+      const devices = await navigator.mediaDevices.enumerateDevices();
+      for (const device of devices) {
+        // Test only one device.
+        if (device.kind == "videoinput") {
+          assert_inherits(device, "getCapabilities");
+          const capabilities = device.getCapabilities();
+          testCapabilities(capabilities, property, "Video device getCapabilities()");
+          break;
+        }
+      }
+    }, "Setup video InputDeviceInfo getCapabilities() test for " + property.name);
+  });
+}
+</script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html
new file mode 100755 (executable)
index 0000000..8ad829e
--- /dev/null
@@ -0,0 +1,222 @@
+<!doctype html>
+<title>MediaStreamTrack GetSettings</title>
+<p class="instructions">When prompted, accept to share your video stream.</p>
+<meta name=timeout content=long>
+<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="permission-helper.js"></script>
+<script>
+  'use strict'
+
+  // https://w3c.github.io/mediacapture-main/archives/20170605/getusermedia.html
+
+  async function createTrackAndGetSettings(t, kind) {
+    const constraints = {};
+    constraints[kind] = true;
+    const stream = await navigator.mediaDevices.getUserMedia(constraints);
+    assert_equals(stream.getTracks().length, 1);
+    t.add_cleanup(() => stream.getTracks()[0].stop());
+    return stream.getTracks()[0].getSettings();
+  }
+
+  promise_test(async t => {
+    await setMediaPermission("granted", ["camera"]);
+    const mediaStream1 = await navigator.mediaDevices.getUserMedia({
+      video: true,
+      audio: false,
+    });
+    t.add_cleanup(() => mediaStream1.getVideoTracks()[0].stop());
+    const settings1 = mediaStream1.getVideoTracks()[0].getSettings();
+
+    const mediaStream2 = await navigator.mediaDevices.getUserMedia({
+      video: {
+        deviceId: {exact: settings1.deviceId},
+      },
+      audio: false
+    });
+    t.add_cleanup(() => mediaStream2.getVideoTracks()[0].stop());
+    const settings2 = mediaStream2.getVideoTracks()[0].getSettings();
+
+    assert_equals(settings1.deviceId, settings2.deviceId);
+  }, 'A device can be opened twice and have the same device ID');
+
+  promise_test(async t => {
+    const mediaStream1 = await navigator.mediaDevices.getUserMedia({
+      video: true,
+      audio: false,
+    });
+    t.add_cleanup(() => mediaStream1.getVideoTracks()[0].stop());
+    const settings1 = mediaStream1.getVideoTracks()[0].getSettings();
+
+    const mediaStream2 = await navigator.mediaDevices.getUserMedia({
+      video: {
+        deviceId: {exact: settings1.deviceId},
+        width: {
+          ideal: settings1.width / 2,
+        },
+      },
+      audio: false
+    });
+    t.add_cleanup(() => mediaStream2.getVideoTracks()[0].stop());
+    const settings2 = mediaStream2.getVideoTracks()[0].getSettings();
+
+    assert_equals(settings1.deviceId, settings2.deviceId);
+    assert_between_inclusive(settings2.width, settings1.width / 2, settings1.width);
+  }, 'A device can be opened twice with different resolutions requested');
+
+  promise_test(async t => {
+    const devices = await navigator.mediaDevices.enumerateDevices();
+    const inputDevices = devices.filter(d => d.kind != "audiooutput");
+    assert_greater_than(inputDevices.length, 0);
+    for (const device of inputDevices) {
+      const device_id_constraint = {deviceId: {exact: device.deviceId}};
+      const constraints = device.kind == "audioinput"
+        ? {audio: device_id_constraint}
+        : {video: device_id_constraint};
+
+      const stream = await navigator.mediaDevices.getUserMedia(constraints);
+      assert_true(stream.getTracks()[0].getSettings().groupId === device.groupId, "device groupId");
+      assert_greater_than(device.groupId.length, 0);
+    }
+  }, 'groupId is correctly reported by getSettings() for all input devices');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "audio");
+    assert_equals(typeof(settings.deviceId), "string",
+                  "deviceId should exist and it should be a string.");
+  }, 'deviceId is reported by getSettings() for getUserMedia() audio tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "audio");
+    assert_equals(typeof(settings.groupId), "string",
+                  "groupId should exist and it should be a string.");
+  }, 'groupId is reported by getSettings() for getUserMedia() audio tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "audio");
+    assert_equals(typeof(settings.sampleRate), "number",
+                  "sampleRate should exist and it should be a number.");
+    assert_greater_than(settings.sampleRate, 0);
+  }, 'sampleRate is reported by getSettings() for getUserMedia() audio tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "audio");
+    assert_equals(typeof(settings.sampleSize), "number",
+                  "sampleSize should exist and it should be a number.");
+    assert_greater_than(settings.sampleSize, 0);
+  }, 'sampleSize is reported by getSettings() for getUserMedia() audio tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "audio");
+    assert_equals(typeof(settings.echoCancellation), "boolean",
+                  "echoCancellation should exist and it should be a boolean.");
+  }, 'echoCancellation is reported by getSettings() for getUserMedia() audio tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "audio");
+    assert_equals(typeof(settings.autoGainControl), "boolean",
+                  "autoGainControl should exist and it should be a boolean.");
+  }, 'autoGainControl is reported by getSettings() for getUserMedia() audio tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "audio");
+    assert_equals(typeof(settings.noiseSuppression), "boolean",
+                  "noiseSuppression should exist and it should be a boolean.");
+  }, 'noiseSuppression is reported by getSettings() for getUserMedia() audio tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "audio");
+    assert_equals(typeof(settings.latency), "number",
+                  "latency should exist and it should be a number.");
+    assert_greater_than_equal(settings.latency,0);
+  }, 'latency is reported by getSettings() for getUserMedia() audio tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "audio");
+    assert_equals(typeof(settings.channelCount), "number",
+                  "channelCount should exist and it should be a number.");
+    assert_greater_than(settings.channelCount, 0);
+  }, 'channelCount is reported by getSettings() for getUserMedia() audio tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "video");
+    assert_equals(typeof(settings.deviceId), "string",
+                  "deviceId should exist and it should be a string.");
+  }, 'deviceId is reported by getSettings() for getUserMedia() video tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "video");
+    assert_equals(typeof(settings.groupId), "string",
+                  "groupId should exist and it should be a string.");
+  }, 'groupId is reported by getSettings() for getUserMedia() video tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "video");
+    assert_equals(typeof(settings.width), "number",
+                  "width should exist and it should be a number.");
+    assert_true(Number.isInteger(settings.width), "width should be an integer.");
+    assert_greater_than_equal(settings.width, 0);;
+  }, 'width is reported by getSettings() for getUserMedia() video tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "video");
+    assert_equals(typeof(settings.height), "number",
+                  "height should exist and it should be a number.");
+    assert_true(Number.isInteger(settings.height), "height should be an integer.");
+    assert_greater_than_equal(settings.height, 0);
+  }, 'height is reported by getSettings() for getUserMedia() video tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "video");
+    assert_equals(typeof(settings.aspectRatio), "number",
+                  "aspectRatio should exist and it should be a number.");
+    assert_greater_than_equal(settings.aspectRatio, 0);
+  }, 'aspectRatio is reported by getSettings() for getUserMedia() video tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "video");
+    assert_equals(typeof(settings.frameRate), "number",
+                  "frameRate should exist and it should be a number.");
+    assert_greater_than_equal(settings.frameRate, 0);
+  }, 'frameRate is reported by getSettings() for getUserMedia() video tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "video");
+    // facingMode not treated as mandatory because not all platforms provide
+    // this information.
+    if (settings.facingMode) {
+      assert_equals(typeof(settings.facingMode), "string",
+                  "If facingMode is provided it should be a string.");
+      assert_in_array(settings.facingMode,
+                  ['user', 'environment', 'left', 'right']);
+    }
+  }, 'facingMode is reported by getSettings() for getUserMedia() video tracks');
+
+  promise_test(async t => {
+    const settings = await createTrackAndGetSettings(t, "video");
+    assert_equals(typeof(settings.resizeMode), "string",
+                "resizeMode should exist and it should be a string.");
+    assert_in_array(settings.resizeMode, ['none', 'crop-and-scale']);
+  }, 'resizeMode is reported by getSettings() for getUserMedia() video tracks');
+
+  promise_test(async t => {
+    const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video : true});
+    const audioTrack = stream.getAudioTracks()[0];
+    const videoTrack = stream.getVideoTracks()[0];
+
+    const audioDeviceId = audioTrack.getSettings().deviceId;
+    const videoDeviceId = videoTrack.getSettings().deviceId;
+    const audioGroupId = audioTrack.getSettings().groupId;
+    const videoGroupId = videoTrack.getSettings().groupId;
+
+    audioTrack.stop();
+    videoTrack.stop();
+
+    assert_equals(audioTrack.getSettings().deviceId, audioDeviceId, "audio track deviceId");
+    assert_equals(videoTrack.getSettings().deviceId, videoDeviceId, "video track deviceId");
+    assert_equals(audioTrack.getSettings().groupId, audioGroupId, "audio track groupId");
+    assert_equals(videoTrack.getSettings().groupId, videoGroupId, "video track groupId");
+  }, 'Stopped tracks should expose deviceId/groupId');
+</script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-id.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-id.https.html
new file mode 100755 (executable)
index 0000000..2250148
--- /dev/null
@@ -0,0 +1,27 @@
+<!doctype html>
+<html>
+<head>
+<title>Distinct id for distinct mediastream tracks</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-MediaStreamTrack-id">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your audio and video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that distinct mediastream tracks have distinct ids.</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+promise_test(async () => {
+  await setMediaPermission();
+  const stream = await navigator.mediaDevices.getUserMedia({video: true, audio: true})
+  assert_not_equals(stream.getVideoTracks()[0], stream.getAudioTracks()[0].id, "audio and video tracks have distinct ids");
+}, "Tests that distinct mediastream tracks have distinct ids ");
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-iframe-transfer.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-iframe-transfer.https.html
new file mode 100755 (executable)
index 0000000..2b3e09e
--- /dev/null
@@ -0,0 +1,30 @@
+<!doctype html>
+<title>MediaStreamTrack transfer to iframe</title>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script>
+promise_test(async () => {
+  const iframe = document.createElement("iframe");
+  const stream = await navigator.mediaDevices.getDisplayMedia({video: true});
+  const track = stream.getVideoTracks()[0];
+  const cloned_track = track.clone();
+  const iframeLoaded = new Promise((resolve) => {iframe.onload = resolve});
+
+  iframe.src = "support/iframe-MediaStreamTrack-transfer.html";
+  document.body.appendChild(iframe);
+
+  await iframeLoaded;
+
+  const nextMessage = new Promise((resolve) => {
+    window.onmessage = resolve
+  });
+
+  assert_not_equals(track.readyState, "ended");
+  iframe.contentWindow.postMessage(track);
+  assert_equals(track.readyState, "ended");
+  assert_equals(cloned_track.readyState, "live");
+
+  const message = await nextMessage;
+  assert_not_equals(message.data.result, 'Failure', 'Failed: ' + message.data.error);
+});
+</script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-init.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-init.https.html
new file mode 100755 (executable)
index 0000000..e5ed490
--- /dev/null
@@ -0,0 +1,39 @@
+<!doctype html>
+<html>
+<head>
+<title>getUserMedia({video:true}) creates a stream with a properly initialized video track</title>
+<link rel="author" title="Dominique Hazael-Massieux" href="mailto:dom@w3.org"/>
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#idl-def-MediaStreamTrack">
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#life-cycle-and-media-flow">
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-MediaStreamTrack-kind">
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-MediaStreamTrack-enabled">
+<link rel="help" href="http://dev.w3.org/2011/webrtc/editor/getusermedia.html#widl-MediaStreamTrack-readyState">
+</head>
+<body>
+<p class="instructions">When prompted, accept to share your video stream.</p>
+<h1 class="instructions">Description</h1>
+<p class="instructions">This test checks that the video track of MediaStream
+object returned by the success callback in getUserMedia is correctly initialized.</p>
+
+<div id='log'></div>
+<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="permission-helper.js"></script>
+<script>
+promise_test(async () => {
+  await setMediaPermission("granted", ["camera"]);
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  const videoTracks = stream.getVideoTracks();
+  assert_equals(videoTracks.length, 1, "There is exactly one video track in the media stream");
+  track = videoTracks[0];
+  assert_equals(track.readyState, "live", "The track object is in live state");
+  assert_equals(track.kind, "video", "The track object is of video kind");
+  // Not clear that this is required by the spec,
+  // see https://www.w3.org/Bugs/Public/show_bug.cgi?id=22212
+  assert_true(track.enabled, "The track object is enabed");
+});
+</script>
+</body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-transfer-video.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-transfer-video.https.html
new file mode 100755 (executable)
index 0000000..9fc2e64
--- /dev/null
@@ -0,0 +1,26 @@
+<!doctype html>
+<title>MediaStreamTrack transfer to iframe</title>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script>
+promise_test(async () => {
+  const iframe = document.createElement("iframe");
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  const track = stream.getVideoTracks()[0];
+  const result = new Promise((resolve, reject) => {
+    window.onmessage = (e) => {
+      if (e.data.result === 'Failure') {
+        reject('Failed: ' + e.data.error);
+      } else {
+        resolve();
+      }
+    };
+  });
+  iframe.addEventListener("load", () => {
+    iframe.contentWindow.postMessage(track);
+  });
+  iframe.src = "support/iframe-MediaStreamTrack-transfer-video.html";
+  document.body.appendChild(iframe);
+  return result;
+});
+</script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-transfer.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-transfer.https.html
new file mode 100755 (executable)
index 0000000..6f6e950
--- /dev/null
@@ -0,0 +1,50 @@
+<!doctype html>
+<title>MediaStreamTrack transfer to Worker</title>
+<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="permission-helper.js"></script>
+<script id="workerCode" type="javascript/worker">
+self.onmessage = (e) => {
+  try {
+    if(e.data instanceof MediaStreamTrack) {
+      self.postMessage({result: 'Success'});
+      return;
+    } else {
+      self.postMessage({
+        result: 'Failure',
+        error: `${e.data} is not a MediaStreamTrack`
+      });
+    }
+  } catch (error) {
+    self.postMessage({
+      result: 'Failure',
+      error
+    });
+  }
+}
+</script>
+<script>
+promise_test(async () => {
+  const workerBlob = new Blob([document.querySelector('#workerCode').textContent],
+                {type: "text/javascript"});
+  const workerUrl = window.URL.createObjectURL(workerBlob);
+  const worker = new Worker(workerUrl);
+  window.URL.revokeObjectURL(workerUrl);
+  await setMediaPermission("granted", ["camera"]);
+  const stream = await navigator.mediaDevices.getUserMedia({video: true});
+  const track = stream.getVideoTracks()[0];
+  const result = new Promise((resolve, reject) => {
+    worker.onmessage = (e) => {
+      if (e.data.result === 'Failure') {
+        reject('Failed: ' + e.data.error);
+      } else {
+        resolve();
+      }
+    };
+  });
+  worker.postMessage(track, [track]);
+  return result;
+});
+</script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html
new file mode 100755 (executable)
index 0000000..6bd64b3
--- /dev/null
@@ -0,0 +1,42 @@
+<!doctype html>
+<title>MediaStreamTrackEvent constructor</title>
+<link rel="help" href="https://w3c.github.io/mediacapture-main/#mediastreamtrackevent">
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<script>
+test(function() {
+  assert_equals(MediaStreamTrackEvent.length, 2);
+  assert_throws_js(TypeError, function() {
+    new MediaStreamTrackEvent("type");
+  });
+  assert_throws_js(TypeError, function() {
+    new MediaStreamTrackEvent("type", null);
+  });
+  assert_throws_js(TypeError, function() {
+    new MediaStreamTrackEvent("type", undefined);
+  });
+}, "The eventInitDict argument is required");
+
+test(function() {
+  assert_throws_js(TypeError, function() {
+    new MediaStreamTrackEvent("type", {});
+  });
+  assert_throws_js(TypeError, function() {
+    new MediaStreamTrackEvent("type", { track: null });
+  });
+  assert_throws_js(TypeError, function() {
+    new MediaStreamTrackEvent("type", { track: undefined });
+  });
+}, "The eventInitDict's track member is required.");
+
+test(function() {
+  // a MediaStreamTrack instance is needed to test, any instance will do.
+  var context = new AudioContext();
+  var dest = context.createMediaStreamDestination();
+  var track = dest.stream.getTracks()[0];
+  assert_true(track instanceof MediaStreamTrack);
+  var event = new MediaStreamTrackEvent("type", { track: track });
+  assert_equals(event.type, "type");
+  assert_equals(event.track, track);
+}, "The MediaStreamTrackEvent instance's track attribute is set.");
+</script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/blank.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/blank.html
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/crashtests/enumerateDevices-after-discard-1.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/crashtests/enumerateDevices-after-discard-1.https.html
new file mode 100755 (executable)
index 0000000..d1f4bab
--- /dev/null
@@ -0,0 +1,18 @@
+<html class="test-wait">
+<head>
+  <title>
+    Test enumerateDevices() calls either side of browsing context discard
+  </title>
+</head>
+<script>
+  const frame = document.createElement('frame');
+  document.documentElement.appendChild(frame);
+  const devices = frame.contentWindow.navigator.mediaDevices;
+  devices.enumerateDevices();
+  frame.remove();
+  devices.enumerateDevices();
+  // Wait long enough to expect the async enumerateDevices() code to complete.
+  navigator.mediaDevices.enumerateDevices().then(
+    () => document.documentElement.removeAttribute("class"));
+</script>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/enumerateDevices-with-navigation.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/enumerateDevices-with-navigation.https.html
new file mode 100755 (executable)
index 0000000..2f7095f
--- /dev/null
@@ -0,0 +1,78 @@
+<!doctype html>
+<title>enumerateDevices() with navigation</title>
+<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="permission-helper.js"></script>
+<body></body>
+<script>
+'use strict';
+const blank_url = 'blank.html';
+const search2 = '?2';
+
+function promise_new_task(t) {
+  return new Promise(resolve => t.step_timeout(resolve, 0));
+}
+function promise_event(target, name) {
+  return new Promise(resolve => target[`on${name}`] = resolve);
+}
+
+promise_test(async t => {
+  // Gecko persists only toplevel documents, so load documents in a toplevel.
+  await test_driver.bless('window.open()');
+  const proxy = window.open(blank_url);
+  t.add_cleanup(() => proxy.close());
+  await promise_event(proxy, 'pageshow');
+  const devices = proxy.navigator.mediaDevices;
+  // Use another task so that another load creates a new session history entry.
+  await promise_new_task(t);
+
+  proxy.location = blank_url + search2;
+  await promise_event(proxy, 'pagehide');
+  // Use another task to ensure the first subdocument is no longer fully
+  // active and proxy refers to the realm of the second document.
+  await promise_new_task(t);
+  assert_equals(proxy.location.search, search2, 'navigated search');
+  // Enumerate from the inactive first Window.
+  const promise_enumerate = devices.enumerateDevices();
+  // `then()` is used rather than static Promise methods because microtasks
+  // for `PromiseResolve()` do not run when Promises from inactive realms are
+  // involved.  Whether microtasks for `then()` run depends on the realm of
+  // the handler rather than the realm of the Promise.
+  // Don't use `finally()`, because it uses `PromiseResolve()` and so
+  // microtasks don't run.
+  // See https://github.com/whatwg/html/issues/5319.
+  let promise_state = 'pending';
+  promise_enumerate.then(() => promise_state = 'resolved',
+                         () => promise_state = 'rejected');
+  // Enumerate in the active second Window to provide enough time to check
+  // that the Promise from the inactive Window does not settle.
+  await proxy.navigator.mediaDevices.enumerateDevices();
+
+  proxy.history.back();
+  await promise_event(proxy, 'pagehide');
+  // enumerateDevices() Promise resolution is triggered only in parallel
+  // steps, so manipulation of the Promise (if the first document was
+  // persisted) would occur through a queued task, which would run after
+  // the pagehide event is dispatched and so after the associated
+  // microtask that runs the following assert.
+  // https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-for-spec-authors
+  assert_equals(promise_state, 'pending', 'Promise state while inactive');
+  // If the first document is restored, then that will occur immediately after
+  // pagehide (and associated microtasks), before the next global task is run.
+  // https://html.spec.whatwg.org/multipage/history.html#traverse-the-history-by-a-delta
+  await promise_new_task(t);
+  if (proxy.navigator.mediaDevices == devices) {
+    // The first document was persisted and restored.
+    assert_equals(proxy.location.search, '', 'history search');
+    await promise_enumerate;
+  } else {
+    // The first document was not restored, but gets re-fetched.
+    await t.step_wait(() => proxy.location.search == '', 'navigation');
+    assert_not_equals(proxy.navigator.mediaDevices, devices, 'new realm')
+    await proxy.navigator.mediaDevices.enumerateDevices();
+    assert_equals(promise_state, 'pending', 'Promise state after discard');
+  }
+}, 'enumerateDevices with navigation');
+</script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html
new file mode 100755 (executable)
index 0000000..31ac1e7
--- /dev/null
@@ -0,0 +1,25 @@
+<!doctype html>
+<title>Historical Media Capture and Streams features</title>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+test(function() {
+  assert_false("mozGetUserMedia" in navigator);
+}, "navigator.mozGetUserMedia should not exist");
+
+test(() => {
+  const mediaStream = new MediaStream();
+  assert_throws_js(TypeError, () => URL.createObjectURL(mediaStream));
+}, "Passing MediaStream to URL.createObjectURL() should throw");
+
+test(() => {
+  const mediaStream = new MediaStream();
+  assert_false("onactive" in mediaStream);
+}, "MediaStream.onactive should not exist");
+
+test(() => {
+  const mediaStream = new MediaStream();
+  assert_false("oninactive" in mediaStream);
+}, "MediaStream.oninactive should not exist");
+</script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/idlharness.https.window.js b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/idlharness.https.window.js
new file mode 100755 (executable)
index 0000000..e8e3cda
--- /dev/null
@@ -0,0 +1,50 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: timeout=long
+
+'use strict';
+
+// https://w3c.github.io/mediacapture-main/
+
+idl_test(
+  ['mediacapture-streams'],
+  ['webidl', 'dom', 'html'],
+  async idl_array => {
+    const inputDevices = [];
+    const outputDevices = [];
+    try {
+      const list = await navigator.mediaDevices.enumerateDevices();
+      for (const device of list) {
+        if (device.kind in self) {
+          continue;
+        }
+        assert_in_array(device.kind, ['audioinput', 'videoinput', 'audiooutput']);
+        self[device.kind] = device;
+        if (device.kind.endsWith('input')) {
+          inputDevices.push(device.kind);
+        } else {
+          outputDevices.push(device.kind);
+        }
+      }
+    } catch (e) {}
+
+    try {
+      self.stream = await navigator.mediaDevices.getUserMedia({audio: true});
+      self.track = stream.getTracks()[0];
+      self.trackEvent = new MediaStreamTrackEvent("type", {
+        track: track,
+      });
+    } catch (e) {}
+
+    idl_array.add_objects({
+      InputDeviceInfo: inputDevices,
+      MediaStream: ['stream', 'new MediaStream()'],
+      Navigator: ['navigator'],
+      MediaDevices: ['navigator.mediaDevices'],
+      MediaDeviceInfo: outputDevices,
+      MediaStreamTrack: ['track'],
+      MediaStreamTrackEvent: ['trackEvent'],
+      OverconstrainedError: ['new OverconstrainedError("constraint")'],
+    });
+  }
+);
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/iframe-enumerate-cleared.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/iframe-enumerate-cleared.html
new file mode 100755 (executable)
index 0000000..27dd046
--- /dev/null
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<script src="message-enumerateddevices.js"></script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/iframe-enumerate.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/iframe-enumerate.html
new file mode 100755 (executable)
index 0000000..27dd046
--- /dev/null
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<script src="message-enumerateddevices.js"></script>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/index.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/index.html
new file mode 100755 (executable)
index 0000000..7e1de80
--- /dev/null
@@ -0,0 +1,51 @@
+<!doctype html>
+<meta name="viewport" content="width=device-width">
+<title>Directory listing for /mediacapture-streams/</title>
+<h1>Directory listing for /mediacapture-streams/</h1>
+<ul>
+<li class="dir"><a href="/">..</a></li>
+<li class="file"><a href="GUM-api.https.html">GUM-api.https.html</a></li>
+<li class="file"><a href="GUM-deny.https.html">GUM-deny.https.html</a></li>
+<li class="file"><a href="GUM-empty-option-param.https.html">GUM-empty-option-param.https.html</a></li>
+<li class="file"><a href="GUM-impossible-constraint.https.html">GUM-impossible-constraint.https.html</a></li>
+<li class="file"><a href="GUM-invalid-facing-mode.https.html">GUM-invalid-facing-mode.https.html</a></li>
+<li class="file"><a href="GUM-non-applicable-constraint.https.html">GUM-non-applicable-constraint.https.html</a></li>
+<li class="file"><a href="GUM-optional-constraint.https.html">GUM-optional-constraint.https.html</a></li>
+<li class="file"><a href="GUM-required-constraint-with-ideal-value.https.html">GUM-required-constraint-with-ideal-value.https.html</a></li>
+<li class="file"><a href="GUM-trivial-constraint.https.html">GUM-trivial-constraint.https.html</a></li>
+<li class="file"><a href="GUM-unknownkey-option-param.https.html">GUM-unknownkey-option-param.https.html</a></li>
+<li class="file"><a href="META.yml">META.yml</a></li>
+<li class="file"><a href="MediaDevices-SecureContext.html">MediaDevices-SecureContext.html</a></li>
+<li class="file"><a href="MediaDevices-after-discard.https.html">MediaDevices-after-discard.https.html</a></li>
+<li class="file"><a href="MediaDevices-enumerateDevices-not-allowed-camera.https.html">MediaDevices-enumerateDevices-not-allowed-camera.https.html</a> (<a href="MediaDevices-enumerateDevices-not-allowed-camera.https.html.headers">.headers</a>)</li>
+<li class="file"><a href="MediaDevices-enumerateDevices-not-allowed-mic.https.html">MediaDevices-enumerateDevices-not-allowed-mic.https.html</a> (<a href="MediaDevices-enumerateDevices-not-allowed-mic.https.html.headers">.headers</a>)</li>
+<li class="file"><a href="MediaDevices-enumerateDevices-returned-objects.https.html">MediaDevices-enumerateDevices-returned-objects.https.html</a></li>
+<li class="file"><a href="MediaDevices-enumerateDevices.https.html">MediaDevices-enumerateDevices.https.html</a></li>
+<li class="file"><a href="MediaDevices-getSupportedConstraints.https.html">MediaDevices-getSupportedConstraints.https.html</a></li>
+<li class="file"><a href="MediaDevices-getUserMedia.https.html">MediaDevices-getUserMedia.https.html</a></li>
+<li class="file"><a href="MediaStream-MediaElement-firstframe.https.html">MediaStream-MediaElement-firstframe.https.html</a></li>
+<li class="file"><a href="MediaStream-MediaElement-preload-none-manual.https.html">MediaStream-MediaElement-preload-none-manual.https.html</a></li>
+<li class="file"><a href="MediaStream-MediaElement-srcObject.https.html">MediaStream-MediaElement-srcObject.https.html</a></li>
+<li class="file"><a href="MediaStream-add-audio-track.https.html">MediaStream-add-audio-track.https.html</a></li>
+<li class="file"><a href="MediaStream-audio-only.https.html">MediaStream-audio-only.https.html</a></li>
+<li class="file"><a href="MediaStream-clone.https.html">MediaStream-clone.https.html</a></li>
+<li class="file"><a href="MediaStream-default-feature-policy.https.html">MediaStream-default-feature-policy.https.html</a></li>
+<li class="file"><a href="MediaStream-finished-add.https.html">MediaStream-finished-add.https.html</a></li>
+<li class="file"><a href="MediaStream-gettrackid.https.html">MediaStream-gettrackid.https.html</a></li>
+<li class="file"><a href="MediaStream-id-manual.https.html">MediaStream-id-manual.https.html</a></li>
+<li class="file"><a href="MediaStream-idl.https.html">MediaStream-idl.https.html</a></li>
+<li class="file"><a href="MediaStream-removetrack.https.html">MediaStream-removetrack.https.html</a></li>
+<li class="file"><a href="MediaStream-supported-by-feature-policy.html">MediaStream-supported-by-feature-policy.html</a></li>
+<li class="file"><a href="MediaStream-video-only.https.html">MediaStream-video-only.https.html</a></li>
+<li class="file"><a href="MediaStreamTrack-MediaElement-disabled-audio-is-silence.https.html">MediaStreamTrack-MediaElement-disabled-audio-is-silence.https.html</a></li>
+<li class="file"><a href="MediaStreamTrack-MediaElement-disabled-video-is-black.https.html">MediaStreamTrack-MediaElement-disabled-video-is-black.https.html</a></li>
+<li class="file"><a href="MediaStreamTrack-applyConstraints.https.html">MediaStreamTrack-applyConstraints.https.html</a></li>
+<li class="file"><a href="MediaStreamTrack-end-manual.https.html">MediaStreamTrack-end-manual.https.html</a></li>
+<li class="file"><a href="MediaStreamTrack-getCapabilities.https.html">MediaStreamTrack-getCapabilities.https.html</a></li>
+<li class="file"><a href="MediaStreamTrack-getSettings.https.html">MediaStreamTrack-getSettings.https.html</a></li>
+<li class="file"><a href="MediaStreamTrack-id.https.html">MediaStreamTrack-id.https.html</a></li>
+<li class="file"><a href="MediaStreamTrack-init.https.html">MediaStreamTrack-init.https.html</a></li>
+<li class="file"><a href="MediaStreamTrackEvent-constructor.https.html">MediaStreamTrackEvent-constructor.https.html</a></li>
+<li class="file"><a href="historical.https.html">historical.https.html</a></li>
+<li class="file"><a href="idlharness.https.window.js">idlharness.https.window.js</a></li>
+</ul>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/message-enumerateddevices.js b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/message-enumerateddevices.js
new file mode 100755 (executable)
index 0000000..4541636
--- /dev/null
@@ -0,0 +1,8 @@
+onmessage = async e => {
+  const stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
+  stream.getTracks().forEach(t => t.stop());
+  const devices = await navigator.mediaDevices.enumerateDevices();
+  e.source.postMessage({
+    devices: devices.map(d => d.toJSON())
+  }, '*');
+}
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/permission-helper.js b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/permission-helper.js
new file mode 100755 (executable)
index 0000000..769f3ee
--- /dev/null
@@ -0,0 +1,24 @@
+// Set permissions for camera and microphone using Web Driver
+// Status can be one of "granted" or "denied"
+// Scope take values from permission names
+async function setMediaPermission(status="granted", scope=["camera", "microphone"]) {
+  try {
+    for (let s of scope) {
+      await test_driver.set_permission({ name: s }, status, true);
+    }
+  } catch (e) {
+    const noSetPermissionSupport = typeof e === "string" && e.match(/set_permission not implemented/);
+    if (!(noSetPermissionSupport ||
+          (e instanceof Error && e.message.match("unimplemented")) )) {
+      throw e;
+    }
+    // Web Driver not implemented action
+    // FF: https://bugzilla.mozilla.org/show_bug.cgi?id=1524074
+
+    // with current WPT runners, will default to granted state for FF and Safari
+    // throw if status!="granted" to invalidate test results
+    if (status === "denied") {
+      assert_implements_optional(!noSetPermissionSupport, "Unable to set permission to denied for this test");
+    }
+  }
+}
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/support/iframe-MediaStreamTrack-transfer-video.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/support/iframe-MediaStreamTrack-transfer-video.html
new file mode 100755 (executable)
index 0000000..9f37ba0
--- /dev/null
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>iframe</title>
+    <script>
+      function onMsg(e) {
+          if(e.data instanceof MediaStreamTrack) {
+              const track = e.data;
+              video = document.getElementById("myvideo");
+              video.srcObject = new MediaStream ([track]);
+              video.play();
+
+              parent.postMessage({result: 'Success'});
+          } else {
+              parent.postMessage({
+                  result: 'Failure',
+                  error: `${e.data} is not a MediaStreamTrack`
+              });
+        }
+      }
+      window.addEventListener("message", onMsg);
+    </script>
+  </head>
+  <body>
+    <video id="myvideo"></video>
+  </body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/support/iframe-MediaStreamTrack-transfer.html b/common/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/support/iframe-MediaStreamTrack-transfer.html
new file mode 100755 (executable)
index 0000000..8273e6e
--- /dev/null
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>iframe</title>
+    <script>
+      function onMsg(e) {
+        alert("0000000000000");
+        if(e.data instanceof MediaStreamTrack) {
+          parent.postMessage({result: 'Success'});
+        } else {
+          parent.postMessage({
+            result: 'Failure',
+            error: `${e.data} is not a MediaStreamTrack`
+          });
+        }
+      }
+      window.addEventListener("message", onMsg);
+    </script>
+  </head>
+  <body>
+  </body>
+</html>
diff --git a/common/tct-mediacapture-w3c-tests/resources/featurepolicy.js b/common/tct-mediacapture-w3c-tests/resources/featurepolicy.js
new file mode 100755 (executable)
index 0000000..864c434
--- /dev/null
@@ -0,0 +1,454 @@
+// Feature test to avoid timeouts
+function assert_feature_policy_supported() {
+  assert_not_equals(document.featurePolicy, undefined,
+                    'Feature Policy is supported');
+}
+// Tests whether a feature that is enabled/disabled by feature policy works
+// as expected.
+// Arguments:
+//    feature_description: a short string describing what feature is being
+//        tested. Examples: "usb.GetDevices()", "PaymentRequest()".
+//    test: test created by testharness. Examples: async_test, promise_test.
+//    src: URL where a feature's availability is checked. Examples:
+//        "/feature-policy/resources/feature-policy-payment.html",
+//        "/feature-policy/resources/feature-policy-usb.html".
+//    expect_feature_available: a callback(data, feature_description) to
+//        verify if a feature is available or unavailable as expected.
+//        The file under the path "src" defines what "data" is sent back as a
+//        postMessage. Inside the callback, some tests (e.g., EXPECT_EQ,
+//        EXPECT_TRUE, etc) are run accordingly to test a feature's
+//        availability.
+//        Example: expect_feature_available_default(data, feature_description).
+//    feature_name: Optional argument, only provided when testing iframe allow
+//      attribute. "feature_name" is the feature name of a policy controlled
+//      feature (https://wicg.github.io/feature-policy/#features).
+//      See examples at:
+//      https://github.com/WICG/feature-policy/blob/master/features.md
+//    allow_attribute: Optional argument, only used for testing fullscreen:
+//      "allowfullscreen"
+function test_feature_availability(
+    feature_description, test, src, expect_feature_available, feature_name,
+    allow_attribute) {
+  let frame = document.createElement('iframe');
+  frame.src = src;
+
+  if (typeof feature_name !== 'undefined') {
+    frame.allow = frame.allow.concat(";" + feature_name);
+  }
+
+  if (typeof allow_attribute !== 'undefined') {
+    frame.setAttribute(allow_attribute, true);
+  }
+
+  window.addEventListener('message', test.step_func(function handler(evt) {
+    if (evt.source === frame.contentWindow) {
+      expect_feature_available(evt.data, feature_description);
+      document.body.removeChild(frame);
+      window.removeEventListener('message', handler);
+      test.done();
+    }
+  }));
+
+  document.body.appendChild(frame);
+}
+
+// Default helper functions to test a feature's availability:
+function expect_feature_available_default(data, feature_description) {
+  assert_true(data.enabled, feature_description);
+}
+
+function expect_feature_unavailable_default(data, feature_description) {
+  assert_false(data.enabled, feature_description);
+}
+
+// This is the same as test_feature_availability() but instead of passing in a
+// function to check the result of the message sent back from an iframe, instead
+// just compares the result to an expected result passed in.
+// Arguments:
+//     test: test created by testharness. Examples: async_test, promise_test.
+//     src: the URL to load in an iframe in which to test the feature.
+//     expected_result: the expected value to compare to the data passed back
+//         from the src page by postMessage.
+//     allow_attribute: Optional argument, only provided when an allow
+//         attribute should be specified on the iframe.
+function test_feature_availability_with_post_message_result(
+    test, src, expected_result, allow_attribute) {
+  var test_result = function(data, feature_description) {
+    assert_equals(data, expected_result);
+  };
+  test_feature_availability(null, test, src, test_result, allow_attribute);
+}
+
+// If this page is intended to test the named feature (according to the URL),
+// tests the feature availability and posts the result back to the parent.
+// Otherwise, does nothing.
+function test_feature_in_iframe(feature_name, feature_promise_factory) {
+  if (location.hash.endsWith(`#${feature_name}`)) {
+    feature_promise_factory().then(
+        () => window.parent.postMessage('#OK', '*'),
+        (e) => window.parent.postMessage('#' + e.name, '*'));
+  }
+}
+
+// Returns true if the URL for this page indicates that it is embedded in an
+// iframe.
+function page_loaded_in_iframe() {
+  return location.hash.startsWith('#iframe');
+}
+
+// Returns a same-origin (relative) URL suitable for embedding in an iframe for
+// testing the availability of the feature.
+function same_origin_url(feature_name) {
+  // Append #iframe to the URL so we can detect the iframe'd version of the
+  // page.
+  return location.pathname + '#iframe#' + feature_name;
+}
+
+// Returns a cross-origin (absolute) URL suitable for embedding in an iframe for
+// testing the availability of the feature.
+function cross_origin_url(base_url, feature_name) {
+  return base_url + same_origin_url(feature_name);
+}
+
+// This function runs all feature policy tests for a particular feature that
+// has a default policy of "self". This includes testing:
+// 1. Feature usage succeeds by default in the top level frame.
+// 2. Feature usage succeeds by default in a same-origin iframe.
+// 3. Feature usage fails by default in a cross-origin iframe.
+// 4. Feature usage suceeds when an allow attribute is specified on a
+//    cross-origin iframe.
+//
+// The same page which called this function will be loaded in the iframe in
+// order to test feature usage there. When this function is called in that
+// context it will simply run the feature and return a result back via
+// postMessage.
+//
+// Arguments:
+//     cross_origin: A cross-origin URL base to be used to load the page which
+//         called into this function.
+//     feature_name: The name of the feature as it should be specified in an
+//         allow attribute.
+//     error_name: If feature usage does not succeed, this is the string
+//         representation of the error that will be passed in the rejected
+//         promise.
+//     feature_promise_factory: A function which returns a promise which tests
+//         feature usage. If usage succeeds, the promise should resolve. If it
+//         fails, the promise should reject with an error that can be
+//         represented as a string.
+function run_all_fp_tests_allow_self(
+    cross_origin, feature_name, error_name, feature_promise_factory) {
+  // This may be the version of the page loaded up in an iframe. If so, just
+  // post the result of running the feature promise back to the parent.
+  if (page_loaded_in_iframe()) {
+    test_feature_in_iframe(feature_name, feature_promise_factory);
+    return;
+  }
+
+  // Run the various tests.
+  // 1. Allowed in top-level frame.
+  promise_test(
+      () => feature_promise_factory(),
+      'Default "' + feature_name +
+          '" feature policy ["self"] allows the top-level document.');
+
+  // 2. Allowed in same-origin iframe.
+  const same_origin_frame_pathname = same_origin_url(feature_name);
+  async_test(
+      t => {
+        test_feature_availability_with_post_message_result(
+            t, same_origin_frame_pathname, '#OK');
+      },
+      'Default "' + feature_name +
+          '" feature policy ["self"] allows same-origin iframes.');
+
+  // 3. Blocked in cross-origin iframe.
+  const cross_origin_frame_url = cross_origin_url(cross_origin, feature_name);
+  async_test(
+      t => {
+        test_feature_availability_with_post_message_result(
+            t, cross_origin_frame_url, '#' + error_name);
+      },
+      'Default "' + feature_name +
+          '" feature policy ["self"] disallows cross-origin iframes.');
+
+  // 4. Allowed in cross-origin iframe with "allow" attribute.
+  async_test(
+      t => {
+        test_feature_availability_with_post_message_result(
+            t, cross_origin_frame_url, '#OK', feature_name);
+      },
+      'Feature policy "' + feature_name +
+          '" can be enabled in cross-origin iframes using "allow" attribute.');
+}
+
+// This function runs all feature policy tests for a particular feature that
+// has a default policy of "*". This includes testing:
+// 1. Feature usage succeeds by default in the top level frame.
+// 2. Feature usage succeeds by default in a same-origin iframe.
+// 3. Feature usage succeeds by default in a cross-origin iframe.
+// 4. Feature usage fails when an allow attribute is specified on a
+//    cross-origin iframe with a value of "feature-name 'none'".
+//
+// The same page which called this function will be loaded in the iframe in
+// order to test feature usage there. When this function is called in that
+// context it will simply run the feature and return a result back via
+// postMessage.
+//
+// Arguments:
+//     cross_origin: A cross-origin URL base to be used to load the page which
+//         called into this function.
+//     feature_name: The name of the feature as it should be specified in an
+//         allow attribute.
+//     error_name: If feature usage does not succeed, this is the string
+//         representation of the error that will be passed in the rejected
+//         promise.
+//     feature_promise_factory: A function which returns a promise which tests
+//         feature usage. If usage succeeds, the promise should resolve. If it
+//         fails, the promise should reject with an error that can be
+//         represented as a string.
+function run_all_fp_tests_allow_all(
+    cross_origin, feature_name, error_name, feature_promise_factory) {
+  // This may be the version of the page loaded up in an iframe. If so, just
+  // post the result of running the feature promise back to the parent.
+  if (page_loaded_in_iframe()) {
+    test_feature_in_iframe(feature_name, feature_promise_factory);
+    return;
+  }
+
+  // Run the various tests.
+  // 1. Allowed in top-level frame.
+  promise_test(
+      () => feature_promise_factory(),
+      'Default "' + feature_name +
+          '" feature policy ["*"] allows the top-level document.');
+
+  // 2. Allowed in same-origin iframe.
+  const same_origin_frame_pathname = same_origin_url(feature_name);
+  async_test(
+      t => {
+        test_feature_availability_with_post_message_result(
+            t, same_origin_frame_pathname, '#OK');
+      },
+      'Default "' + feature_name +
+          '" feature policy ["*"] allows same-origin iframes.');
+
+  // 3. Allowed in cross-origin iframe.
+  const cross_origin_frame_url = cross_origin_url(cross_origin, feature_name);
+  async_test(
+      t => {
+        test_feature_availability_with_post_message_result(
+            t, cross_origin_frame_url, '#OK');
+      },
+      'Default "' + feature_name +
+          '" feature policy ["*"] allows cross-origin iframes.');
+
+  // 4. Blocked in cross-origin iframe with "allow" attribute set to 'none'.
+  async_test(
+      t => {
+        test_feature_availability_with_post_message_result(
+            t, cross_origin_frame_url, '#' + error_name,
+            feature_name + " 'none'");
+      },
+      'Feature policy "' + feature_name +
+          '" can be disabled in cross-origin iframes using "allow" attribute.');
+
+  // 5. Blocked in same-origin iframe with "allow" attribute set to 'none'.
+  async_test(
+      t => {
+        test_feature_availability_with_post_message_result(
+            t, same_origin_frame_pathname, '#' + error_name,
+            feature_name + " 'none'");
+      },
+      'Feature policy "' + feature_name +
+          '" can be disabled in same-origin iframes using "allow" attribute.');
+}
+
+// This function tests that a given policy allows each feature for the correct
+// list of origins specified by the |expected_policy|.
+// Arguments:
+//     expected_policy: A list of {feature, allowlist} pairs where the feature is
+//         enabled for every origin in the allowlist, in the |policy|.
+//     policy: Either a document.featurePolicy or an iframe.featurePolicy to be
+//         tested.
+//     message: A short description of what policy is being tested.
+function test_allowlists(expected_policy, policy, message) {
+  for (var allowlist of allowlists) {
+    test(function() {
+      assert_array_equals(
+        policy.getAllowlistForFeature(allowlist.feature),
+        allowlist.allowlist);
+    }, message + ' for feature ' + allowlist.feature);
+  }
+}
+
+// This function tests that a subframe's document policy allows a given feature.
+// A feature is allowed in a frame either through inherited policy or specified
+// by iframe allow attribute.
+// Arguments:
+//     test: test created by testharness. Examples: async_test, promise_test.
+//     feature: feature name that should be allowed in the frame.
+//     src: the URL to load in the frame.
+//     allow: the allow attribute (container policy) of the iframe
+function test_allowed_feature_for_subframe(message, feature, src, allow) {
+  let frame = document.createElement('iframe');
+  if (typeof allow !== 'undefined') {
+    frame.allow = allow;
+  }
+  promise_test(function() {
+    assert_feature_policy_supported();
+    frame.src = src;
+    return new Promise(function(resolve, reject) {
+      window.addEventListener('message', function handler(evt) {
+        resolve(evt.data);
+      }, { once: true });
+      document.body.appendChild(frame);
+    }).then(function(data) {
+      assert_true(data.includes(feature), feature);
+    });
+  }, message);
+}
+
+// This function tests that a subframe's document policy disallows a given
+// feature. A feature is allowed in a frame either through inherited policy or
+// specified by iframe allow attribute.
+// Arguments:
+//     test: test created by testharness. Examples: async_test, promise_test.
+//     feature: feature name that should not be allowed in the frame.
+//     src: the URL to load in the frame.
+//     allow: the allow attribute (container policy) of the iframe
+function test_disallowed_feature_for_subframe(message, feature, src, allow) {
+  let frame = document.createElement('iframe');
+  if (typeof allow !== 'undefined') {
+    frame.allow = allow;
+  }
+  promise_test(function() {
+    assert_feature_policy_supported();
+    frame.src = src;
+    return new Promise(function(resolve, reject) {
+      window.addEventListener('message', function handler(evt) {
+        resolve(evt.data);
+      }, { once: true });
+      document.body.appendChild(frame);
+    }).then(function(data) {
+      assert_false(data.includes(feature), feature);
+    });
+  }, message);
+}
+
+// This function tests that a subframe with header policy defined on a given
+// feature allows and disallows the feature as expected.
+// Arguments:
+//     feature: feature name.
+//     frame_header_policy: either *, 'self' or 'none', defines the frame
+//                          document's header policy on |feature|.
+//     src: the URL to load in the frame.
+//     test_expects: contains 6 expected results of either |feature| is allowed
+//                   or not inside of a local or remote iframe nested inside
+//                   the subframe given the header policy to be either *,
+//                   'slef', or 'none'.
+//     test_name: name of the test.
+function test_subframe_header_policy(
+    feature, frame_header_policy, src, test_expects, test_name) {
+  let frame = document.createElement('iframe');
+  promise_test(function() {
+    assert_feature_policy_supported()
+    frame.src = src + '?pipe=sub|header(Feature-Policy,' + feature + ' '
+        + frame_header_policy + ';)';
+    return new Promise(function(resolve) {
+      window.addEventListener('message', function handler(evt) {
+        resolve(evt.data);
+      });
+      document.body.appendChild(frame);
+    }).then(function(results) {
+      for (var j = 0; j < results.length; j++) {
+        var data = results[j];
+
+        function test_result(message, test_expect) {
+          if (test_expect) {
+            assert_true(data.allowedfeatures.includes(feature), message);
+          } else {
+            assert_false(data.allowedfeatures.includes(feature), message);
+          }
+        }
+
+        if (data.frame === 'local') {
+          if (data.policy === '*') {
+            test_result('local_all:', test_expects.local_all);
+          }
+          if (data.policy === '\'self\'') {
+            test_result('local_self:', test_expects.local_self);
+          }
+          if (data.policy === '\'none\'') {
+            test_result('local_none:', test_expects.local_none);
+          }
+        }
+
+        if (data.frame === 'remote') {
+          if (data.policy === '*') {
+            test_result('remote_all:', test_expects.remote_all);
+          }
+          if (data.policy === '\'self\'') {
+            test_result('remote_self:', test_expects.remote_self);
+          }
+          if (data.policy === '\'none\'') {
+            test_result('remote_none:', test_expects.remote_none);
+          }
+        }
+      }
+    });
+  }, test_name);
+}
+
+// This function tests that frame policy allows a given feature correctly. A
+// feature is allowed in a frame either through inherited policy or specified
+// by iframe allow attribute.
+// Arguments:
+//     feature: feature name.
+//     src: the URL to load in the frame. If undefined, the iframe will have a
+//         srcdoc="" attribute
+//     test_expect: boolean value of whether the feature should be allowed.
+//     allow: optional, the allow attribute (container policy) of the iframe.
+//     allowfullscreen: optional, boolean value of allowfullscreen attribute.
+//     sandbox: optional boolean. If true, the frame will be sandboxed (with
+//         allow-scripts, so that tests can run in it.)
+function test_frame_policy(
+    feature, src, srcdoc, test_expect, allow, allowfullscreen, sandbox) {
+  let frame = document.createElement('iframe');
+  document.body.appendChild(frame);
+  // frame_policy should be dynamically updated as allow and allowfullscreen is
+  // updated.
+  var frame_policy = frame.featurePolicy;
+  if (typeof allow !== 'undefined') {
+    frame.setAttribute('allow', allow);
+  }
+  if (!!allowfullscreen) {
+    frame.setAttribute('allowfullscreen', true);
+  }
+  if (!!sandbox) {
+    frame.setAttribute('sandbox', 'allow-scripts');
+  }
+  if (!!src) {
+    frame.src = src;
+  }
+  if (!!srcdoc) {
+    frame.srcdoc = "<h1>Hello world!</h1>";
+  }
+  if (test_expect) {
+    assert_true(frame_policy.allowedFeatures().includes(feature));
+  } else {
+    assert_false(frame_policy.allowedFeatures().includes(feature));
+  }
+}
+
+function expect_reports(report_count, policy_name, description) {
+  async_test(t => {
+    var num_received_reports = 0;
+    new ReportingObserver(t.step_func((reports, observer) => {
+        const relevant_reports = reports.filter(r => (r.body.featureId === policy_name));
+        num_received_reports += relevant_reports.length;
+        if (num_received_reports >= report_count) {
+            t.done();
+        }
+   }), {types: ['permissions-policy-violation'], buffered: true}).observe();
+  }, description);
+}
diff --git a/common/tct-mediacapture-w3c-tests/resources/get-host-info.sub.js b/common/tct-mediacapture-w3c-tests/resources/get-host-info.sub.js
new file mode 100755 (executable)
index 0000000..ca5ff05
--- /dev/null
@@ -0,0 +1,63 @@
+/**
+ * Host information for cross-origin tests.
+ * @returns {Object} with properties for different host information.
+ */
+function get_host_info() {
+
+  var HTTP_PORT = '80';
+  var HTTP_PORT2 = '81';
+  var HTTPS_PORT = '443';
+  var HTTPS_PORT2 = '444';
+  var PROTOCOL = self.location.protocol;
+  var IS_HTTPS = (PROTOCOL == "https:");
+  var PORT = IS_HTTPS ? HTTPS_PORT : HTTP_PORT;
+  var PORT2 = IS_HTTPS ? HTTPS_PORT2 : HTTP_PORT2;
+  var HTTP_PORT_ELIDED = HTTP_PORT == "80" ? "" : (":" + HTTP_PORT);
+  var HTTP_PORT2_ELIDED = HTTP_PORT2 == "80" ? "" : (":" + HTTP_PORT2);
+  var HTTPS_PORT_ELIDED = HTTPS_PORT == "443" ? "" : (":" + HTTPS_PORT);
+  var PORT_ELIDED = IS_HTTPS ? HTTPS_PORT_ELIDED : HTTP_PORT_ELIDED;
+  var ORIGINAL_HOST = 'w3c-test.org';
+  var REMOTE_HOST = (ORIGINAL_HOST === 'localhost') ? '127.0.0.1' : ('www.' + ORIGINAL_HOST);
+  var OTHER_HOST = 'www2.w3c-test.org';
+  var NOTSAMESITE_HOST = (ORIGINAL_HOST === 'localhost') ? '127.0.0.1' : ('not-web-platform.test');
+
+  return {
+    HTTP_PORT: HTTP_PORT,
+    HTTP_PORT2: HTTP_PORT2,
+    HTTPS_PORT: HTTPS_PORT,
+    HTTPS_PORT2: HTTPS_PORT2,
+    PORT: PORT,
+    PORT2: PORT2,
+    ORIGINAL_HOST: ORIGINAL_HOST,
+    REMOTE_HOST: REMOTE_HOST,
+
+    ORIGIN: PROTOCOL + "//" + ORIGINAL_HOST + PORT_ELIDED,
+    HTTP_ORIGIN: 'http://' + ORIGINAL_HOST + HTTP_PORT_ELIDED,
+    HTTPS_ORIGIN: 'https://' + ORIGINAL_HOST + HTTPS_PORT_ELIDED,
+    HTTPS_ORIGIN_WITH_CREDS: 'https://foo:bar@' + ORIGINAL_HOST + HTTPS_PORT_ELIDED,
+    HTTP_ORIGIN_WITH_DIFFERENT_PORT: 'http://' + ORIGINAL_HOST + HTTP_PORT2_ELIDED,
+    REMOTE_ORIGIN: PROTOCOL + "//" + REMOTE_HOST + PORT_ELIDED,
+    OTHER_ORIGIN: PROTOCOL + "//" + OTHER_HOST + PORT_ELIDED,
+    HTTP_REMOTE_ORIGIN: 'http://' + REMOTE_HOST + HTTP_PORT_ELIDED,
+    HTTP_NOTSAMESITE_ORIGIN: 'http://' + NOTSAMESITE_HOST + HTTP_PORT_ELIDED,
+    HTTP_REMOTE_ORIGIN_WITH_DIFFERENT_PORT: 'http://' + REMOTE_HOST + HTTP_PORT2_ELIDED,
+    HTTPS_REMOTE_ORIGIN: 'https://' + REMOTE_HOST + HTTPS_PORT_ELIDED,
+    HTTPS_REMOTE_ORIGIN_WITH_CREDS: 'https://foo:bar@' + REMOTE_HOST + HTTPS_PORT_ELIDED,
+    HTTPS_NOTSAMESITE_ORIGIN: 'https://' + NOTSAMESITE_HOST + HTTPS_PORT_ELIDED,
+    UNAUTHENTICATED_ORIGIN: 'http://' + OTHER_HOST + HTTP_PORT_ELIDED,
+    AUTHENTICATED_ORIGIN: 'https://' + OTHER_HOST + HTTPS_PORT_ELIDED
+  };
+}
+
+/**
+ * When a default port is used, location.port returns the empty string.
+ * This function attempts to provide an exact port, assuming we are running under wptserve.
+ * @param {*} loc - can be Location/<a>/<area>/URL, but assumes http/https only.
+ * @returns {string} The port number.
+ */
+function get_port(loc) {
+  if (loc.port) {
+    return loc.port;
+  }
+  return loc.protocol === 'https:' ? '443' : '80';
+}
diff --git a/common/tct-mediacapture-w3c-tests/resources/testdriver-vendor.js b/common/tct-mediacapture-w3c-tests/resources/testdriver-vendor.js
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/common/tct-mediacapture-w3c-tests/resources/testdriver.js b/common/tct-mediacapture-w3c-tests/resources/testdriver.js
new file mode 100755 (executable)
index 0000000..9f2bfb0
--- /dev/null
@@ -0,0 +1,734 @@
+(function() {
+    "use strict";
+    var idCounter = 0;
+    let testharness_context = null;
+
+    function getInViewCenterPoint(rect) {
+        var left = Math.max(0, rect.left);
+        var right = Math.min(window.innerWidth, rect.right);
+        var top = Math.max(0, rect.top);
+        var bottom = Math.min(window.innerHeight, rect.bottom);
+
+        var x = 0.5 * (left + right);
+        var y = 0.5 * (top + bottom);
+
+        return [x, y];
+    }
+
+    function getPointerInteractablePaintTree(element) {
+        let elementDocument = element.ownerDocument;
+        if (!elementDocument.contains(element)) {
+            return [];
+        }
+
+        var rectangles = element.getClientRects();
+
+        if (rectangles.length === 0) {
+            return [];
+        }
+
+        var centerPoint = getInViewCenterPoint(rectangles[0]);
+
+        if ("elementsFromPoint" in elementDocument) {
+            return elementDocument.elementsFromPoint(centerPoint[0], centerPoint[1]);
+        } else if ("msElementsFromPoint" in elementDocument) {
+            var rv = elementDocument.msElementsFromPoint(centerPoint[0], centerPoint[1]);
+            return Array.prototype.slice.call(rv ? rv : []);
+        } else {
+            throw new Error("document.elementsFromPoint unsupported");
+        }
+    }
+
+    function inView(element) {
+        var pointerInteractablePaintTree = getPointerInteractablePaintTree(element);
+        return pointerInteractablePaintTree.indexOf(element) !== -1;
+    }
+
+
+    /**
+     * @namespace {test_driver}
+     */
+    window.test_driver = {
+        /**
+         * Set the context in which testharness.js is loaded
+         *
+         * @param {WindowProxy} context - the window containing testharness.js
+         **/
+        set_test_context: function(context) {
+          if (window.test_driver_internal.set_test_context) {
+            window.test_driver_internal.set_test_context(context);
+          }
+          testharness_context = context;
+        },
+
+        /**
+         * postMessage to the context containing testharness.js
+         *
+         * @param {Object} msg - the data to POST
+         **/
+        message_test: function(msg) {
+            let target = testharness_context;
+            if (testharness_context === null) {
+                target = window;
+            }
+            target.postMessage(msg, "*");
+        },
+
+        /**
+         * Trigger user interaction in order to grant additional privileges to
+         * a provided function.
+         *
+         * See `triggered by user activation
+         * <https://html.spec.whatwg.org/#triggered-by-user-activation>`_.
+         *
+         * @example
+         * var mediaElement = document.createElement('video');
+         *
+         * test_driver.bless('initiate media playback', function () {
+         *   mediaElement.play();
+         * });
+         *
+         * @param {String} intent - a description of the action which must be
+         *                          triggered by user interaction
+         * @param {Function} action - code requiring escalated privileges
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} fulfilled following user interaction and
+         *                    execution of the provided `action` function;
+         *                    rejected if interaction fails or the provided
+         *                    function throws an error
+         */
+        bless: function(intent, action, context=null) {
+            let contextDocument = context ? context.document : document;
+            var button = contextDocument.createElement("button");
+            button.innerHTML = "This test requires user interaction.<br />" +
+                "Please click here to allow " + intent + ".";
+            button.id = "wpt-test-driver-bless-" + (idCounter += 1);
+            const elem = contextDocument.body || contextDocument.documentElement;
+            elem.appendChild(button);
+
+            let wait_click = new Promise(resolve => button.addEventListener("click", resolve));
+
+            return test_driver.click(button)
+                .then(wait_click)
+                .then(function() {
+                    button.remove();
+
+                    if (typeof action === "function") {
+                        return action();
+                    }
+                    return null;
+                });
+        },
+
+        /**
+         * Triggers a user-initiated click
+         *
+         * If ``element`` isn't inside the
+         * viewport, it will be scrolled into view before the click
+         * occurs.
+         *
+         * If ``element`` is from a different browsing context, the
+         * command will be run in that context.
+         *
+         * Matches the behaviour of the `Element Click
+         * <https://w3c.github.io/webdriver/#element-click>`_
+         * WebDriver command.
+         *
+         * **Note:** If the element to be clicked does not have a
+         * unique ID, the document must not have any DOM mutations
+         * made between the function being called and the promise
+         * settling.
+         *
+         * @param {Element} element - element to be clicked
+         * @returns {Promise} fulfilled after click occurs, or rejected in
+         *                    the cases the WebDriver command errors
+         */
+        click: function(element) {
+            if (!inView(element)) {
+                element.scrollIntoView({behavior: "instant",
+                                        block: "end",
+                                        inline: "nearest"});
+            }
+
+            var pointerInteractablePaintTree = getPointerInteractablePaintTree(element);
+            if (pointerInteractablePaintTree.length === 0 ||
+                !element.contains(pointerInteractablePaintTree[0])) {
+                return Promise.reject(new Error("element click intercepted error"));
+            }
+
+            var rect = element.getClientRects()[0];
+            var centerPoint = getInViewCenterPoint(rect);
+            return window.test_driver_internal.click(element,
+                                                     {x: centerPoint[0],
+                                                      y: centerPoint[1]});
+        },
+
+        /**
+         * Deletes all cookies.
+         *
+         * Matches the behaviour of the `Delete All Cookies
+         * <https://w3c.github.io/webdriver/#delete-all-cookies>`_
+         * WebDriver command.
+         *
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} fulfilled after cookies are deleted, or rejected in
+         *                    the cases the WebDriver command errors
+         */
+        delete_all_cookies: function(context=null) {
+            return window.test_driver_internal.delete_all_cookies(context);
+        },
+
+        /**
+         * Send keys to an element.
+         *
+         * If ``element`` isn't inside the
+         * viewport, it will be scrolled into view before the click
+         * occurs.
+         *
+         * If ``element`` is from a different browsing context, the
+         * command will be run in that context.
+         *
+         * To send special keys, send the respective key's codepoint,
+         * as defined by `WebDriver
+         * <https://w3c.github.io/webdriver/#keyboard-actions>`_.  For
+         * example, the "tab" key is represented as "``\uE004``".
+         *
+         * **Note:** these special-key codepoints are not necessarily
+         * what you would expect. For example, <kbd>Esc</kbd> is the
+         * invalid Unicode character ``\uE00C``, not the ``\u001B`` Escape
+         * character from ASCII.
+         *
+         * This matches the behaviour of the
+         * `Send Keys
+         * <https://w3c.github.io/webdriver/#element-send-keys>`_
+         * WebDriver command.
+         *
+         * **Note:** If the element to be clicked does not have a
+         * unique ID, the document must not have any DOM mutations
+         * made between the function being called and the promise
+         * settling.
+         *
+         * @param {Element} element - element to send keys to
+         * @param {String} keys - keys to send to the element
+         * @returns {Promise} fulfilled after keys are sent, or rejected in
+         *                    the cases the WebDriver command errors
+         */
+        send_keys: function(element, keys) {
+            if (!inView(element)) {
+                element.scrollIntoView({behavior: "instant",
+                                        block: "end",
+                                        inline: "nearest"});
+            }
+
+            var pointerInteractablePaintTree = getPointerInteractablePaintTree(element);
+            if (pointerInteractablePaintTree.length === 0 ||
+                !element.contains(pointerInteractablePaintTree[0])) {
+                return Promise.reject(new Error("element send_keys intercepted error"));
+            }
+
+            return window.test_driver_internal.send_keys(element, keys);
+        },
+
+        /**
+         * Freeze the current page
+         *
+         * The freeze function transitions the page from the HIDDEN state to
+         * the FROZEN state as described in `Lifecycle API for Web Pages
+         * <https://github.com/WICG/page-lifecycle/blob/master/README.md>`_.
+         *
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} fulfilled after the freeze request is sent, or rejected
+         *                    in case the WebDriver command errors
+         */
+        freeze: function(context=null) {
+            return window.test_driver_internal.freeze();
+        },
+
+        /**
+         * Minimizes the browser window.
+         *
+         * Matches the the behaviour of the `Minimize
+         * <https://www.w3.org/TR/webdriver/#minimize-window>`_
+         * WebDriver command
+         *
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} fulfilled with the previous {@link
+         *                    https://www.w3.org/TR/webdriver/#dfn-windowrect-object|WindowRect}
+         *                      value, after the window is minimized.
+         */
+        minimize_window: function(context=null) {
+            return window.test_driver_internal.minimize_window(context);
+        },
+
+        /**
+         * Restore the window from minimized/maximized state to a given rect.
+         *
+         * Matches the behaviour of the `Set Window Rect
+         * <https://www.w3.org/TR/webdriver/#set-window-rect>`_
+         * WebDriver command
+         *
+         * @param {Object} rect - A {@link
+         *                           https://www.w3.org/TR/webdriver/#dfn-windowrect-object|WindowRect}
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} fulfilled after the window is restored to the given rect.
+         */
+        set_window_rect: function(rect, context=null) {
+            return window.test_driver_internal.set_window_rect(rect, context);
+        },
+
+        /**
+         * Send a sequence of actions
+         *
+         * This function sends a sequence of actions to perform.
+         *
+         * Matches the behaviour of the `Actions
+         * <https://w3c.github.io/webdriver/#actions>`_ feature in
+         * WebDriver.
+         *
+         * Authors are encouraged to use the
+         * :js:class:`test_driver.Actions` builder rather than
+         * invoking this API directly.
+         *
+         * @param {Array} actions - an array of actions. The format is
+         *                          the same as the actions property
+         *                          of the `Perform Actions
+         *                          <https://w3c.github.io/webdriver/#perform-actions>`_
+         *                          WebDriver command. Each element is
+         *                          an object representing an input
+         *                          source and each input source
+         *                          itself has an actions property
+         *                          detailing the behaviour of that
+         *                          source at each timestep (or
+         *                          tick). Authors are not expected to
+         *                          construct the actions sequence by
+         *                          hand, but to use the builder api
+         *                          provided in testdriver-actions.js
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} fulfilled after the actions are performed, or rejected in
+         *                    the cases the WebDriver command errors
+         */
+        action_sequence: function(actions, context=null) {
+            return window.test_driver_internal.action_sequence(actions, context);
+        },
+
+        /**
+         * Generates a test report on the current page
+         *
+         * The generate_test_report function generates a report (to be
+         * observed by ReportingObserver) for testing purposes.
+         *
+         * Matches the `Generate Test Report
+         * <https://w3c.github.io/reporting/#generate-test-report-command>`_
+         * WebDriver command.
+         *
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} fulfilled after the report is generated, or
+         *                    rejected if the report generation fails
+         */
+        generate_test_report: function(message, context=null) {
+            return window.test_driver_internal.generate_test_report(message, context);
+        },
+
+        /**
+         * Sets the state of a permission
+         *
+         * This function simulates a user setting a permission into a
+         * particular state.
+         *
+         * Matches the `Set Permission
+         * <https://w3c.github.io/permissions/#set-permission-command>`_
+         * WebDriver command.
+         *
+         * @example
+         * await test_driver.set_permission({ name: "background-fetch" }, "denied");
+         * await test_driver.set_permission({ name: "push", userVisibleOnly: true }, "granted", true);
+         *
+         * @param {Object} descriptor - a `PermissionDescriptor
+         *                              <https://w3c.github.io/permissions/#dictdef-permissiondescriptor>`_
+         *                              object
+         * @param {String} state - the state of the permission
+         * @param {boolean} one_realm - Optional. Whether the permission applies to only one realm
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         * @returns {Promise} fulfilled after the permission is set, or rejected if setting the
+         *                    permission fails
+         */
+        set_permission: function(descriptor, state, one_realm=false, context=null) {
+            let permission_params = {
+              descriptor,
+              state,
+              oneRealm: one_realm,
+            };
+            return window.test_driver_internal.set_permission(permission_params, context);
+            
+        },
+
+        /**
+         * Creates a virtual authenticator
+         *
+         * This function creates a virtual authenticator for use with
+         * the U2F and WebAuthn APIs.
+         *
+         * Matches the `Add Virtual Authenticator
+         * <https://w3c.github.io/webauthn/#sctn-automation-add-virtual-authenticator>`_
+         * WebDriver command.
+         *
+         * @param {Object} config - an `Authenticator Configuration
+         *                          <https://w3c.github.io/webauthn/#authenticator-configuration>`_
+         *                          object
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} fulfilled after the authenticator is added, or
+         *                    rejected in the cases the WebDriver command
+         *                    errors. Returns the ID of the authenticator
+         */
+        add_virtual_authenticator: function(config, context=null) {
+            return window.test_driver_internal.add_virtual_authenticator(config, context);
+        },
+
+        /**
+         * Removes a virtual authenticator
+         *
+         * This function removes a virtual authenticator that has been
+         * created by :js:func:`add_virtual_authenticator`.
+         *
+         * Matches the `Remove Virtual Authenticator
+         * <https://w3c.github.io/webauthn/#sctn-automation-remove-virtual-authenticator>`_
+         * WebDriver command.
+         *
+         * @param {String} authenticator_id - the ID of the authenticator to be
+         *                                    removed.
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} fulfilled after the authenticator is removed, or
+         *                    rejected in the cases the WebDriver command
+         *                    errors
+         */
+        remove_virtual_authenticator: function(authenticator_id, context=null) {
+            return window.test_driver_internal.remove_virtual_authenticator(authenticator_id, context);
+        },
+
+        /**
+         * Adds a credential to a virtual authenticator
+         *
+         * Matches the `Add Credential
+         * <https://w3c.github.io/webauthn/#sctn-automation-add-credential>`_
+         * WebDriver command.
+         *
+         * @param {String} authenticator_id - the ID of the authenticator
+         * @param {Object} credential - A `Credential Parameters
+         *                              <https://w3c.github.io/webauthn/#credential-parameters>`_
+         *                              object
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} fulfilled after the credential is added, or
+         *                    rejected in the cases the WebDriver command
+         *                    errors
+         */
+        add_credential: function(authenticator_id, credential, context=null) {
+            return window.test_driver_internal.add_credential(authenticator_id, credential, context);
+        },
+
+        /**
+         * Gets all the credentials stored in an authenticator
+         *
+         * This function retrieves all the credentials (added via the U2F API,
+         * WebAuthn, or the add_credential function) stored in a virtual
+         * authenticator
+         *
+         * Matches the `Get Credentials
+         * <https://w3c.github.io/webauthn/#sctn-automation-get-credentials>`_
+         * WebDriver command.
+         *
+         * @param {String} authenticator_id - the ID of the authenticator
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} fulfilled after the credentials are
+         *                    returned, or rejected in the cases the
+         *                    WebDriver command errors. Returns an
+         *                    array of `Credential Parameters
+         *                    <https://w3c.github.io/webauthn/#credential-parameters>`_
+         */
+        get_credentials: function(authenticator_id, context=null) {
+            return window.test_driver_internal.get_credentials(authenticator_id, context=null);
+        },
+
+        /**
+         * Remove a credential stored in an authenticator
+         *
+         * Matches the `Remove Credential
+         * <https://w3c.github.io/webauthn/#sctn-automation-remove-credential>`_
+         * WebDriver command.
+         *
+         * @param {String} authenticator_id - the ID of the authenticator
+         * @param {String} credential_id - the ID of the credential
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} fulfilled after the credential is removed, or
+         *                    rejected in the cases the WebDriver command
+         *                    errors.
+         */
+        remove_credential: function(authenticator_id, credential_id, context=null) {
+            return window.test_driver_internal.remove_credential(authenticator_id, credential_id, context);
+        },
+
+        /**
+         * Removes all the credentials stored in a virtual authenticator
+         *
+         * Matches the `Remove All Credentials
+         * <https://w3c.github.io/webauthn/#sctn-automation-remove-all-credentials>`_
+         * WebDriver command.
+         *
+         * @param {String} authenticator_id - the ID of the authenticator
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} fulfilled after the credentials are removed, or
+         *                    rejected in the cases the WebDriver command
+         *                    errors.
+         */
+        remove_all_credentials: function(authenticator_id, context=null) {
+            return window.test_driver_internal.remove_all_credentials(authenticator_id, context);
+        },
+
+        /**
+         * Sets the User Verified flag on an authenticator
+         *
+         * Sets whether requests requiring user verification will succeed or
+         * fail on a given virtual authenticator
+         *
+         * Matches the `Set User Verified
+         * <https://w3c.github.io/webauthn/#sctn-automation-set-user-verified>`_
+         * WebDriver command.
+         *
+         * @param {String} authenticator_id - the ID of the authenticator
+         * @param {boolean} uv - the User Verified flag
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         */
+        set_user_verified: function(authenticator_id, uv, context=null) {
+            return window.test_driver_internal.set_user_verified(authenticator_id, uv, context);
+        },
+
+        /**
+         * Sets the storage access rule for an origin when embedded
+         * in a third-party context.
+         *
+         * Matches the `Set Storage Access
+         * <https://privacycg.github.io/storage-access/#set-storage-access-command>`_
+         * WebDriver command.
+         *
+         * @param {String} origin - A third-party origin to block or allow.
+         *                          May be "*" to indicate all origins.
+         * @param {String} embedding_origin - an embedding (first-party) origin
+         *                                    on which {origin}'s access should
+         *                                    be blocked or allowed.
+         *                                    May be "*" to indicate all origins.
+         * @param {String} state - The storage access setting.
+         *                         Must be either "allowed" or "blocked".
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} Fulfilled after the storage access rule has been
+         *                    set, or rejected if setting the rule fails.
+         */
+        set_storage_access: function(origin, embedding_origin, state, context=null) {
+            if (state !== "allowed" && state !== "blocked") {
+                throw new Error("storage access status must be 'allowed' or 'blocked'");
+            }
+            const blocked = state === "blocked";
+            return window.test_driver_internal.set_storage_access(origin, embedding_origin, blocked, context);
+        },
+
+        /**
+         * Sets the current transaction automation mode for Secure Payment
+         * Confirmation.
+         *
+         * This function places `Secure Payment
+         * Confirmation <https://w3c.github.io/secure-payment-confirmation>`_ into
+         * an automated 'autoaccept' or 'autoreject' mode, to allow testing
+         * without user interaction with the transaction UX prompt.
+         *
+         * Matches the `Set SPC Transaction Mode
+         * <https://w3c.github.io/secure-payment-confirmation/#sctn-automation-set-spc-transaction-mode>`_
+         * WebDriver command.
+         *
+         * @example
+         * await test_driver.set_spc_transaction_mode("autoaccept");
+         * test.add_cleanup(() => {
+         *   return test_driver.set_spc_transaction_mode("none");
+         * });
+         *
+         * // Assumption: `request` is a PaymentRequest with a secure-payment-confirmation
+         * // payment method.
+         * const response = await request.show();
+         *
+         * @param {String} mode - The `transaction mode
+         *                        <https://w3c.github.io/secure-payment-confirmation/#enumdef-transactionautomationmode>`_
+         *                        to set. Must be one of "``none``",
+         *                        "``autoaccept``", or
+         *                        "``autoreject``".
+         * @param {WindowProxy} context - Browsing context in which
+         *                                to run the call, or null for the current
+         *                                browsing context.
+         *
+         * @returns {Promise} Fulfilled after the transaction mode has been set,
+         *                    or rejected if setting the mode fails.
+         */
+        set_spc_transaction_mode: function(mode, context=null) {
+          return window.test_driver_internal.set_spc_transaction_mode(mode, context);
+        },
+    };
+
+    window.test_driver_internal = {
+        /**
+         * This flag should be set to `true` by any code which implements the
+         * internal methods defined below for automation purposes. Doing so
+         * allows the library to signal failure immediately when an automated
+         * implementation of one of the methods is not available.
+         */
+        in_automation: false,
+
+        click: function(element, coords) {
+            if (this.in_automation) {
+                return Promise.reject(new Error('Not implemented'));
+            }
+
+            return new Promise(function(resolve, reject) {
+                element.addEventListener("click", resolve);
+            });
+        },
+
+        delete_all_cookies: function(context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        send_keys: function(element, keys) {
+            if (this.in_automation) {
+                return Promise.reject(new Error('Not implemented'));
+            }
+
+            return new Promise(function(resolve, reject) {
+                var seen = "";
+
+                function remove() {
+                    element.removeEventListener("keydown", onKeyDown);
+                }
+
+                function onKeyDown(event) {
+                    if (event.key.length > 1) {
+                        return;
+                    }
+
+                    seen += event.key;
+
+                    if (keys.indexOf(seen) !== 0) {
+                        reject(new Error("Unexpected key sequence: " + seen));
+                        remove();
+                    } else if (seen === keys) {
+                        resolve();
+                        remove();
+                    }
+                }
+
+                element.addEventListener("keydown", onKeyDown);
+            });
+        },
+
+        freeze: function(context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        minimize_window: function(context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        set_window_rect: function(rect, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        action_sequence: function(actions, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        generate_test_report: function(message, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        set_permission: function(permission_params, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        add_virtual_authenticator: function(config, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        remove_virtual_authenticator: function(authenticator_id, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        add_credential: function(authenticator_id, credential, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        get_credentials: function(authenticator_id, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        remove_credential: function(authenticator_id, credential_id, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        remove_all_credentials: function(authenticator_id, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        set_user_verified: function(authenticator_id, uv, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        set_storage_access: function(origin, embedding_origin, blocked, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+        set_spc_transaction_mode: function(mode, context=null) {
+            return Promise.reject(new Error("unimplemented"));
+        },
+
+    };
+})();
index ecc050c28ed7c5b7275c511d0a1a0c5979d9540d..d35e297aab0714ca00fd27607369a653977cb9ae 100755 (executable)
@@ -1,20 +1,11 @@
 /*global self*/
 /*jshint latedef: nofunc*/
-/*
-Distributed under both the W3C Test Suite License [1] and the W3C
-3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
-policies and contribution forms [3].
 
-[1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
-[2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
-[3] http://www.w3.org/2004/10/27-testcases
-*/
+/* Documentation: https://web-platform-tests.org/writing-tests/testharness-api.html
+ * (../docs/_writing-tests/testharness-api.md) */
 
-/* Documentation is in docs/api.md */
-
-(function ()
+(function (global_scope)
 {
-    var debug = false;
     // default timeout is 10 seconds, test can override if needed
     var settings = {
         output:true,
@@ -22,7 +13,9 @@ policies and contribution forms [3].
             "normal":10000,
             "long":60000
         },
-        test_timeout:null
+        test_timeout:null,
+        message_events: ["start", "test_state", "result", "completion"],
+        debug: false,
     };
 
     var xhtml_ns = "http://www.w3.org/1999/xhtml";
@@ -46,9 +39,6 @@ policies and contribution forms [3].
      *
      *   // Should return the test harness timeout duration in milliseconds.
      *   float test_timeout();
-     *
-     *   // Should return the global scope object.
-     *   object global_scope();
      * };
      */
 
@@ -64,21 +54,76 @@ policies and contribution forms [3].
         this.output_handler = null;
         this.all_loaded = false;
         var this_obj = this;
+        this.message_events = [];
+        this.dispatched_messages = [];
+
+        this.message_functions = {
+            start: [add_start_callback, remove_start_callback,
+                    function (properties) {
+                        this_obj._dispatch("start_callback", [properties],
+                                           {type: "start", properties: properties});
+                    }],
+
+            test_state: [add_test_state_callback, remove_test_state_callback,
+                         function(test) {
+                             this_obj._dispatch("test_state_callback", [test],
+                                                {type: "test_state",
+                                                 test: test.structured_clone()});
+                         }],
+            result: [add_result_callback, remove_result_callback,
+                     function (test) {
+                         this_obj.output_handler.show_status();
+                         this_obj._dispatch("result_callback", [test],
+                                            {type: "result",
+                                             test: test.structured_clone()});
+                     }],
+            completion: [add_completion_callback, remove_completion_callback,
+                         function (tests, harness_status, asserts) {
+                             var cloned_tests = map(tests, function(test) {
+                                 return test.structured_clone();
+                             });
+                             this_obj._dispatch("completion_callback", [tests, harness_status],
+                                                {type: "complete",
+                                                 tests: cloned_tests,
+                                                 status: harness_status.structured_clone(),
+                                                 asserts: asserts.map(assert => assert.structured_clone())});
+                         }]
+        }
+
         on_event(window, 'load', function() {
             this_obj.all_loaded = true;
         });
+
+        on_event(window, 'message', function(event) {
+            if (event.data && event.data.type === "getmessages" && event.source) {
+                // A window can post "getmessages" to receive a duplicate of every
+                // message posted by this environment so far. This allows subscribers
+                // from fetch_tests_from_window to 'catch up' to the current state of
+                // this environment.
+                for (var i = 0; i < this_obj.dispatched_messages.length; ++i)
+                {
+                    event.source.postMessage(this_obj.dispatched_messages[i], "*");
+                }
+            }
+        });
     }
 
     WindowTestEnvironment.prototype._dispatch = function(selector, callback_args, message_arg) {
+        this.dispatched_messages.push(message_arg);
         this._forEach_windows(
-                function(w, is_same_origin) {
-                    if (is_same_origin && selector in w) {
+                function(w, same_origin) {
+                    if (same_origin) {
                         try {
-                            w[selector].apply(undefined, callback_args);
-                        } catch (e) {
-                            if (debug) {
-                                throw e;
-                            }
+                            var has_selector = selector in w;
+                        } catch(e) {
+                            // If document.domain was set at some point same_origin can be
+                            // wrong and the above will fail.
+                            has_selector = false;
+                        }
+                        if (has_selector) {
+                            try {
+                                w[selector].apply(undefined, callback_args);
+                            } catch (e) {}
                         }
                     }
                     if (supports_post_message(w) && w !== self) {
@@ -88,9 +133,9 @@ policies and contribution forms [3].
     };
 
     WindowTestEnvironment.prototype._forEach_windows = function(callback) {
-        // Iterate of the the windows [self ... top, opener]. The callback is passed
-        // two objects, the first one is the windows object itself, the second one
-        // is a boolean indicating whether or not its on the same origin as the
+        // Iterate over the windows [self ... top, opener]. The callback is passed
+        // two objects, the first one is the window object itself, the second one
+        // is a boolean indicating whether or not it's on the same origin as the
         // current window.
         var cache = this.window_cache;
         if (!cache) {
@@ -98,33 +143,14 @@ policies and contribution forms [3].
             var w = self;
             var i = 0;
             var so;
-            var origins = location.ancestorOrigins;
             while (w != w.parent) {
                 w = w.parent;
-                // In WebKit, calls to parent windows' properties that aren't on the same
-                // origin cause an error message to be displayed in the error console but
-                // don't throw an exception. This is a deviation from the current HTML5
-                // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504
-                // The problem with WebKit's behavior is that it pollutes the error console
-                // with error messages that can't be caught.
-                //
-                // This issue can be mitigated by relying on the (for now) proprietary
-                // `location.ancestorOrigins` property which returns an ordered list of
-                // the origins of enclosing windows. See:
-                // http://trac.webkit.org/changeset/113945.
-                if (origins) {
-                    so = (location.origin == origins[i]);
-                } else {
-                    so = is_same_origin(w);
-                }
+                so = is_same_origin(w);
                 cache.push([w, so]);
                 i++;
             }
             w = window.opener;
             if (w) {
-                // window.opener isn't included in the `location.ancestorOrigins` prop.
-                // We'll just have to deal with a simple check and an error msg on WebKit
-                // browsers in this case.
                 cache.push([w, is_same_origin(w)]);
             }
             this.window_cache = cache;
@@ -141,41 +167,50 @@ policies and contribution forms [3].
         this.output_handler = output;
 
         var this_obj = this;
+
         add_start_callback(function (properties) {
             this_obj.output_handler.init(properties);
-            this_obj._dispatch("start_callback", [properties],
-                           { type: "start", properties: properties });
         });
+
         add_test_state_callback(function(test) {
             this_obj.output_handler.show_status();
-            this_obj._dispatch("test_state_callback", [test],
-                               { type: "test_state", test: test.structured_clone() });
         });
+
         add_result_callback(function (test) {
             this_obj.output_handler.show_status();
-            this_obj._dispatch("result_callback", [test],
-                               { type: "result", test: test.structured_clone() });
         });
-        add_completion_callback(function (tests, harness_status) {
-            this_obj.output_handler.show_results(tests, harness_status);
-            var cloned_tests = map(tests, function(test) { return test.structured_clone(); });
-            this_obj._dispatch("completion_callback", [tests, harness_status],
-                               { type: "complete", tests: cloned_tests,
-                                 status: harness_status.structured_clone() });
+
+        add_completion_callback(function (tests, harness_status, asserts_run) {
+            this_obj.output_handler.show_results(tests, harness_status, asserts_run);
         });
+        this.setup_messages(settings.message_events);
     };
 
+    WindowTestEnvironment.prototype.setup_messages = function(new_events) {
+        var this_obj = this;
+        forEach(settings.message_events, function(x) {
+            var current_dispatch = this_obj.message_events.indexOf(x) !== -1;
+            var new_dispatch = new_events.indexOf(x) !== -1;
+            if (!current_dispatch && new_dispatch) {
+                this_obj.message_functions[x][0](this_obj.message_functions[x][2]);
+            } else if (current_dispatch && !new_dispatch) {
+                this_obj.message_functions[x][1](this_obj.message_functions[x][2]);
+            }
+        });
+        this.message_events = new_events;
+    }
+
     WindowTestEnvironment.prototype.next_default_test_name = function() {
-        //Don't use document.title to work around an Opera bug in XHTML documents
-        var title = document.getElementsByTagName("title")[0];
-        var prefix = (title && title.firstChild && title.firstChild.data) || "Untitled";
         var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
         this.name_counter++;
-        return prefix + suffix;
+        return get_title() + suffix;
     };
 
     WindowTestEnvironment.prototype.on_new_harness_properties = function(properties) {
         this.output_handler.setup(properties);
+        if (properties.hasOwnProperty("message_events")) {
+            this.setup_messages(properties.message_events);
+        }
     };
 
     WindowTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
@@ -195,10 +230,6 @@ policies and contribution forms [3].
         return settings.harness_timeout.normal;
     };
 
-    WindowTestEnvironment.prototype.global_scope = function() {
-        return window;
-    };
-
     /*
      * Base TestEnvironment implementation for a generic web worker.
      *
@@ -242,7 +273,7 @@ policies and contribution forms [3].
     WorkerTestEnvironment.prototype.next_default_test_name = function() {
         var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
         this.name_counter++;
-        return "Untitled" + suffix;
+        return get_title() + suffix;
     };
 
     WorkerTestEnvironment.prototype.on_new_harness_properties = function() {};
@@ -271,14 +302,15 @@ policies and contribution forms [3].
                     });
                 });
         add_completion_callback(
-                function(tests, harness_status) {
+                function(tests, harness_status, asserts) {
                     this_obj._dispatch({
                         type: "complete",
                         tests: map(tests,
                             function(test) {
                                 return test.structured_clone();
                             }),
-                        status: harness_status.structured_clone()
+                        status: harness_status.structured_clone(),
+                        asserts: asserts.map(assert => assert.structured_clone()),
                     });
                 });
     };
@@ -291,10 +323,6 @@ policies and contribution forms [3].
         return null;
     };
 
-    WorkerTestEnvironment.prototype.global_scope = function() {
-        return self;
-    };
-
     /*
      * Dedicated web workers.
      * https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobalscope
@@ -333,7 +361,7 @@ policies and contribution forms [3].
         self.addEventListener("connect",
                 function(message_event) {
                     this_obj._add_message_port(message_event.source);
-                });
+                }, false);
     }
     SharedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
 
@@ -358,25 +386,34 @@ policies and contribution forms [3].
         var this_obj = this;
         self.addEventListener("message",
                 function(event) {
-                    if (event.data.type && event.data.type === "connect") {
-                        this_obj._add_message_port(event.ports[0]);
-                        event.ports[0].start();
+                    if (event.data && event.data.type && event.data.type === "connect") {
+                        this_obj._add_message_port(event.source);
                     }
-                });
+                }, false);
 
         // The oninstall event is received after the service worker script and
         // all imported scripts have been fetched and executed. It's the
         // equivalent of an onload event for a document. All tests should have
         // been added by the time this event is received, thus it's not
-        // necessary to wait until the onactivate event.
-        on_event(self, "install",
-                function(event) {
-                    this_obj.all_loaded = true;
-                    if (this_obj.on_loaded_callback) {
-                        this_obj.on_loaded_callback();
-                    }
-                });
+        // necessary to wait until the onactivate event. However, tests for
+        // installed service workers need another event which is equivalent to
+        // the onload event because oninstall is fired only on installation. The
+        // onmessage event is used for that purpose since tests using
+        // testharness.js should ask the result to its service worker by
+        // PostMessage. If the onmessage event is triggered on the service
+        // worker's context, that means the worker's script has been evaluated.
+        on_event(self, "install", on_all_loaded);
+        on_event(self, "message", on_all_loaded);
+        function on_all_loaded() {
+            if (this_obj.all_loaded)
+                return;
+            this_obj.all_loaded = true;
+            if (this_obj.on_loaded_callback) {
+              this_obj.on_loaded_callback();
+            }
+        }
     }
+
     ServiceWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
 
     ServiceWorkerTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
@@ -387,45 +424,153 @@ policies and contribution forms [3].
         }
     };
 
+    /*
+     * JavaScript shells.
+     *
+     * This class is used as the test_environment when testharness is running
+     * inside a JavaScript shell.
+     */
+    function ShellTestEnvironment() {
+        this.name_counter = 0;
+        this.all_loaded = false;
+        this.on_loaded_callback = null;
+        Promise.resolve().then(function() {
+            this.all_loaded = true
+            if (this.on_loaded_callback) {
+                this.on_loaded_callback();
+            }
+        }.bind(this));
+        this.message_list = [];
+        this.message_ports = [];
+    }
+
+    ShellTestEnvironment.prototype.next_default_test_name = function() {
+        var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
+        this.name_counter++;
+        return "Untitled" + suffix;
+    };
+
+    ShellTestEnvironment.prototype.on_new_harness_properties = function() {};
+
+    ShellTestEnvironment.prototype.on_tests_ready = function() {};
+
+    ShellTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
+        if (this.all_loaded) {
+            callback();
+        } else {
+            this.on_loaded_callback = callback;
+        }
+    };
+
+    ShellTestEnvironment.prototype.test_timeout = function() {
+        // Tests running in a shell don't have a default timeout, so behave as
+        // if settings.explicit_timeout is true.
+        return null;
+    };
+
     function create_test_environment() {
-        if ('document' in self) {
+        if ('document' in global_scope) {
             return new WindowTestEnvironment();
         }
-        if ('DedicatedWorkerGlobalScope' in self &&
-            self instanceof DedicatedWorkerGlobalScope) {
+        if ('DedicatedWorkerGlobalScope' in global_scope &&
+            global_scope instanceof DedicatedWorkerGlobalScope) {
             return new DedicatedWorkerTestEnvironment();
         }
-        if ('SharedWorkerGlobalScope' in self &&
-            self instanceof SharedWorkerGlobalScope) {
+        if ('SharedWorkerGlobalScope' in global_scope &&
+            global_scope instanceof SharedWorkerGlobalScope) {
             return new SharedWorkerTestEnvironment();
         }
-        if ('ServiceWorkerGlobalScope' in self &&
-            self instanceof ServiceWorkerGlobalScope) {
+        if ('ServiceWorkerGlobalScope' in global_scope &&
+            global_scope instanceof ServiceWorkerGlobalScope) {
             return new ServiceWorkerTestEnvironment();
         }
-        throw new Error("Unsupported test environment");
+        if ('WorkerGlobalScope' in global_scope &&
+            global_scope instanceof WorkerGlobalScope) {
+            return new DedicatedWorkerTestEnvironment();
+        }
+
+        return new ShellTestEnvironment();
     }
 
     var test_environment = create_test_environment();
 
     function is_shared_worker(worker) {
-        return 'SharedWorker' in self && worker instanceof SharedWorker;
+        return 'SharedWorker' in global_scope && worker instanceof SharedWorker;
     }
 
     function is_service_worker(worker) {
-        return 'ServiceWorker' in self && worker instanceof ServiceWorker;
+        // The worker object may be from another execution context,
+        // so do not use instanceof here.
+        return 'ServiceWorker' in global_scope &&
+            Object.prototype.toString.call(worker) == '[object ServiceWorker]';
+    }
+
+    var seen_func_name = Object.create(null);
+
+    function get_test_name(func, name)
+    {
+        if (name) {
+            return name;
+        }
+
+        if (func) {
+            var func_code = func.toString();
+
+            // Try and match with brackets, but fallback to matching without
+            var arrow = func_code.match(/^\(\)\s*=>\s*(?:{(.*)}\s*|(.*))$/);
+
+            // Check for JS line separators
+            if (arrow !== null && !/[\u000A\u000D\u2028\u2029]/.test(func_code)) {
+                var trimmed = (arrow[1] !== undefined ? arrow[1] : arrow[2]).trim();
+                // drop trailing ; if there's no earlier ones
+                trimmed = trimmed.replace(/^([^;]*)(;\s*)+$/, "$1");
+
+                if (trimmed) {
+                    let name = trimmed;
+                    if (seen_func_name[trimmed]) {
+                        // This subtest name already exists, so add a suffix.
+                        name += " " + seen_func_name[trimmed];
+                    } else {
+                        seen_func_name[trimmed] = 0;
+                    }
+                    seen_func_name[trimmed] += 1;
+                    return name;
+                }
+            }
+        }
+
+        return test_environment.next_default_test_name();
     }
 
     /*
      * API functions
      */
-
     function test(func, name, properties)
     {
-        var test_name = name ? name : test_environment.next_default_test_name();
-        properties = properties ? properties : {};
+        if (tests.promise_setup_called) {
+            tests.status.status = tests.status.ERROR;
+            tests.status.message = '`test` invoked after `promise_setup`';
+            tests.complete();
+        }
+        var test_name = get_test_name(func, name);
         var test_obj = new Test(test_name, properties);
-        test_obj.step(func, test_obj, test_obj);
+        var value = test_obj.step(func, test_obj, test_obj);
+
+        if (value !== undefined) {
+            var msg = 'Test named "' + test_name +
+                '" passed a function to `test` that returned a value.';
+
+            try {
+                if (value && typeof value.then === 'function') {
+                    msg += ' Consider using `promise_test` instead when ' +
+                        'using Promises or async/await.';
+                }
+            } catch (err) {}
+
+            tests.status.status = tests.status.ERROR;
+            tests.status.message = msg;
+        }
+
         if (test_obj.phase === test_obj.phases.STARTED) {
             test_obj.done();
         }
@@ -433,37 +578,305 @@ policies and contribution forms [3].
 
     function async_test(func, name, properties)
     {
+        if (tests.promise_setup_called) {
+            tests.status.status = tests.status.ERROR;
+            tests.status.message = '`async_test` invoked after `promise_setup`';
+            tests.complete();
+        }
         if (typeof func !== "function") {
             properties = name;
             name = func;
             func = null;
         }
-        var test_name = name ? name : test_environment.next_default_test_name();
-        properties = properties ? properties : {};
+        var test_name = get_test_name(func, name);
         var test_obj = new Test(test_name, properties);
         if (func) {
-            test_obj.step(func, test_obj, test_obj);
+            var value = test_obj.step(func, test_obj, test_obj);
+
+            // Test authors sometimes return values to async_test, expecting us
+            // to handle the value somehow. Make doing so a harness error to be
+            // clear this is invalid, and point authors to promise_test if it
+            // may be appropriate.
+            //
+            // Note that we only perform this check on the initial function
+            // passed to async_test, not on any later steps - we haven't seen a
+            // consistent problem with those (and it's harder to check).
+            if (value !== undefined) {
+                var msg = 'Test named "' + test_name +
+                    '" passed a function to `async_test` that returned a value.';
+
+                try {
+                    if (value && typeof value.then === 'function') {
+                        msg += ' Consider using `promise_test` instead when ' +
+                            'using Promises or async/await.';
+                    }
+                } catch (err) {}
+
+                tests.set_status(tests.status.ERROR, msg);
+                tests.complete();
+            }
         }
         return test_obj;
     }
 
     function promise_test(func, name, properties) {
-        var test = async_test(name, properties);
-        Promise.resolve(test.step(func, test, test))
-            .then(
-                function() {
-                    test.done();
-                })
-            .catch(test.step_func(
-                function(value) {
-                    if (value instanceof AssertionError) {
-                        throw value;
-                    }
-                    assert(false, "promise_test", null,
-                           "Unhandled rejection with value: ${value}", {value:value});
-                }));
+        if (typeof func !== "function") {
+            properties = name;
+            name = func;
+            func = null;
+        }
+        var test_name = get_test_name(func, name);
+        var test = new Test(test_name, properties);
+        test._is_promise_test = true;
+
+        // If there is no promise tests queue make one.
+        if (!tests.promise_tests) {
+            tests.promise_tests = Promise.resolve();
+        }
+        tests.promise_tests = tests.promise_tests.then(function() {
+            return new Promise(function(resolve) {
+                var promise = test.step(func, test, test);
+
+                test.step(function() {
+                    assert(!!promise, "promise_test", null,
+                           "test body must return a 'thenable' object (received ${value})",
+                           {value:promise});
+                    assert(typeof promise.then === "function", "promise_test", null,
+                           "test body must return a 'thenable' object (received an object with no `then` method)",
+                           null);
+                });
+
+                // Test authors may use the `step` method within a
+                // `promise_test` even though this reflects a mixture of
+                // asynchronous control flow paradigms. The "done" callback
+                // should be registered prior to the resolution of the
+                // user-provided Promise to avoid timeouts in cases where the
+                // Promise does not settle but a `step` function has thrown an
+                // error.
+                add_test_done_callback(test, resolve);
+
+                Promise.resolve(promise)
+                    .catch(test.step_func(
+                        function(value) {
+                            if (value instanceof AssertionError) {
+                                throw value;
+                            }
+                            assert(false, "promise_test", null,
+                                   "Unhandled rejection with value: ${value}", {value:value});
+                        }))
+                    .then(function() {
+                        test.done();
+                    });
+                });
+        });
     }
 
+    /**
+     * Make a copy of a Promise in the current realm.
+     *
+     * @param {Promise} promise the given promise that may be from a different
+     *                          realm
+     * @returns {Promise}
+     *
+     * An arbitrary promise provided by the caller may have originated in
+     * another frame that have since navigated away, rendering the frame's
+     * document inactive. Such a promise cannot be used with `await` or
+     * Promise.resolve(), as microtasks associated with it may be prevented
+     * from being run. See https://github.com/whatwg/html/issues/5319 for a
+     * particular case.
+     *
+     * In functions we define here, there is an expectation from the caller
+     * that the promise is from the current realm, that can always be used with
+     * `await`, etc. We therefore create a new promise in this realm that
+     * inherit the value and status from the given promise.
+     */
+
+    function bring_promise_to_current_realm(promise) {
+        return new Promise(promise.then.bind(promise));
+    }
+
+    function promise_rejects_js(test, constructor, promise, description) {
+        return bring_promise_to_current_realm(promise)
+            .then(test.unreached_func("Should have rejected: " + description))
+            .catch(function(e) {
+                assert_throws_js_impl(constructor, function() { throw e },
+                                      description, "promise_rejects_js");
+            });
+    }
+
+    /**
+     * Assert that a Promise is rejected with the right DOMException.
+     *
+     * @param test the test argument passed to promise_test
+     * @param {number|string} type.  See documentation for assert_throws_dom.
+     *
+     * For the remaining arguments, there are two ways of calling
+     * promise_rejects_dom:
+     *
+     * 1) If the DOMException is expected to come from the current global, the
+     * third argument should be the promise expected to reject, and a fourth,
+     * optional, argument is the assertion description.
+     *
+     * 2) If the DOMException is expected to come from some other global, the
+     * third argument should be the DOMException constructor from that global,
+     * the fourth argument the promise expected to reject, and the fifth,
+     * optional, argument the assertion description.
+     */
+
+    function promise_rejects_dom(test, type, promiseOrConstructor, descriptionOrPromise, maybeDescription) {
+        let constructor, promise, description;
+        if (typeof promiseOrConstructor === "function" &&
+            promiseOrConstructor.name === "DOMException") {
+            constructor = promiseOrConstructor;
+            promise = descriptionOrPromise;
+            description = maybeDescription;
+        } else {
+            constructor = self.DOMException;
+            promise = promiseOrConstructor;
+            description = descriptionOrPromise;
+            assert(maybeDescription === undefined,
+                   "Too many args pased to no-constructor version of promise_rejects_dom");
+        }
+        return bring_promise_to_current_realm(promise)
+            .then(test.unreached_func("Should have rejected: " + description))
+            .catch(function(e) {
+                assert_throws_dom_impl(type, function() { throw e }, description,
+                                       "promise_rejects_dom", constructor);
+            });
+    }
+
+    function promise_rejects_exactly(test, exception, promise, description) {
+        return bring_promise_to_current_realm(promise)
+            .then(test.unreached_func("Should have rejected: " + description))
+            .catch(function(e) {
+                assert_throws_exactly_impl(exception, function() { throw e },
+                                           description, "promise_rejects_exactly");
+            });
+    }
+
+    /**
+     * This constructor helper allows DOM events to be handled using Promises,
+     * which can make it a lot easier to test a very specific series of events,
+     * including ensuring that unexpected events are not fired at any point.
+     */
+    function EventWatcher(test, watchedNode, eventTypes, timeoutPromise)
+    {
+        if (typeof eventTypes == 'string') {
+            eventTypes = [eventTypes];
+        }
+
+        var waitingFor = null;
+
+        // This is null unless we are recording all events, in which case it
+        // will be an Array object.
+        var recordedEvents = null;
+
+        var eventHandler = test.step_func(function(evt) {
+            assert_true(!!waitingFor,
+                        'Not expecting event, but got ' + evt.type + ' event');
+            assert_equals(evt.type, waitingFor.types[0],
+                          'Expected ' + waitingFor.types[0] + ' event, but got ' +
+                          evt.type + ' event instead');
+
+            if (Array.isArray(recordedEvents)) {
+                recordedEvents.push(evt);
+            }
+
+            if (waitingFor.types.length > 1) {
+                // Pop first event from array
+                waitingFor.types.shift();
+                return;
+            }
+            // We need to null out waitingFor before calling the resolve function
+            // since the Promise's resolve handlers may call wait_for() which will
+            // need to set waitingFor.
+            var resolveFunc = waitingFor.resolve;
+            waitingFor = null;
+            // Likewise, we should reset the state of recordedEvents.
+            var result = recordedEvents || evt;
+            recordedEvents = null;
+            resolveFunc(result);
+        });
+
+        for (var i = 0; i < eventTypes.length; i++) {
+            watchedNode.addEventListener(eventTypes[i], eventHandler, false);
+        }
+
+        /**
+         * Returns a Promise that will resolve after the specified event or
+         * series of events has occurred.
+         *
+         * @param options An optional options object. If the 'record' property
+         *                on this object has the value 'all', when the Promise
+         *                returned by this function is resolved,  *all* Event
+         *                objects that were waited for will be returned as an
+         *                array.
+         *
+         * For example,
+         *
+         * ```js
+         * const watcher = new EventWatcher(t, div, [ 'animationstart',
+         *                                            'animationiteration',
+         *                                            'animationend' ]);
+         * return watcher.wait_for([ 'animationstart', 'animationend' ],
+         *                         { record: 'all' }).then(evts => {
+         *   assert_equals(evts[0].elapsedTime, 0.0);
+         *   assert_equals(evts[1].elapsedTime, 2.0);
+         * });
+         * ```
+         */
+        this.wait_for = function(types, options) {
+            if (waitingFor) {
+                return Promise.reject('Already waiting for an event or events');
+            }
+            if (typeof types == 'string') {
+                types = [types];
+            }
+            if (options && options.record && options.record === 'all') {
+                recordedEvents = [];
+            }
+            return new Promise(function(resolve, reject) {
+                var timeout = test.step_func(function() {
+                    // If the timeout fires after the events have been received
+                    // or during a subsequent call to wait_for, ignore it.
+                    if (!waitingFor || waitingFor.resolve !== resolve)
+                        return;
+
+                    // This should always fail, otherwise we should have
+                    // resolved the promise.
+                    assert_true(waitingFor.types.length == 0,
+                                'Timed out waiting for ' + waitingFor.types.join(', '));
+                    var result = recordedEvents;
+                    recordedEvents = null;
+                    var resolveFunc = waitingFor.resolve;
+                    waitingFor = null;
+                    resolveFunc(result);
+                });
+
+                if (timeoutPromise) {
+                    timeoutPromise().then(timeout);
+                }
+
+                waitingFor = {
+                    types: types,
+                    resolve: resolve,
+                    reject: reject
+                };
+            });
+        };
+
+        function stop_watching() {
+            for (var i = 0; i < eventTypes.length; i++) {
+                watchedNode.removeEventListener(eventTypes[i], eventHandler, false);
+            }
+        };
+
+        test._add_cleanup(stop_watching);
+
+        return this;
+    }
+    expose(EventWatcher, 'EventWatcher');
+
     function setup(func_or_properties, maybe_properties)
     {
         var func = null;
@@ -480,11 +893,60 @@ policies and contribution forms [3].
         test_environment.on_new_harness_properties(properties);
     }
 
+    function promise_setup(func, maybe_properties)
+    {
+        if (typeof func !== "function") {
+            tests.set_status(tests.status.ERROR,
+                             "promise_test invoked without a function");
+            tests.complete();
+            return;
+        }
+        tests.promise_setup_called = true;
+
+        if (!tests.promise_tests) {
+            tests.promise_tests = Promise.resolve();
+        }
+
+        tests.promise_tests = tests.promise_tests
+            .then(function()
+                  {
+                      var properties = maybe_properties || {};
+                      var result;
+
+                      tests.setup(null, properties);
+                      result = func();
+                      test_environment.on_new_harness_properties(properties);
+
+                      if (!result || typeof result.then !== "function") {
+                          throw "Non-thenable returned by function passed to `promise_setup`";
+                      }
+                      return result;
+                  })
+            .catch(function(e)
+                   {
+                       tests.set_status(tests.status.ERROR,
+                                        String(e),
+                                        e && e.stack);
+                       tests.complete();
+                   });
+    }
+
     function done() {
         if (tests.tests.length === 0) {
-            tests.set_file_is_test();
+            // `done` is invoked after handling uncaught exceptions, so if the
+            // harness status is already set, the corresponding message is more
+            // descriptive than the generic message defined here.
+            if (tests.status.status === null) {
+                tests.status.status = tests.status.ERROR;
+                tests.status.message = "done() was called without first defining any tests";
+            }
+
+            tests.complete();
+            return;
         }
         if (tests.file_is_test) {
+            // file is test files never have asynchronous cleanup logic,
+            // meaning the fully-synchronous `done` function can be used here.
             tests.tests[0].done();
         }
         tests.end_wait();
@@ -503,18 +965,37 @@ policies and contribution forms [3].
                 });
     }
 
+    /*
+     * Register a function as a DOM event listener to the given object for the
+     * event bubbling phase.
+     *
+     * This function was deprecated in November of 2019.
+     */
     function on_event(object, event, callback)
     {
         object.addEventListener(event, callback, false);
     }
 
+    function step_timeout(f, t) {
+        var outer_this = this;
+        var args = Array.prototype.slice.call(arguments, 2);
+        return setTimeout(function() {
+            f.apply(outer_this, args);
+        }, t * tests.timeout_multiplier);
+    }
+
     expose(test, 'test');
     expose(async_test, 'async_test');
     expose(promise_test, 'promise_test');
+    expose(promise_rejects_js, 'promise_rejects_js');
+    expose(promise_rejects_dom, 'promise_rejects_dom');
+    expose(promise_rejects_exactly, 'promise_rejects_exactly');
     expose(generate_tests, 'generate_tests');
     expose(setup, 'setup');
+    expose(promise_setup, 'promise_setup');
     expose(done, 'done');
     expose(on_event, 'on_event');
+    expose(step_timeout, 'step_timeout');
 
     /*
      * Return a string truncated to the given length, with ... added at the end
@@ -537,10 +1018,17 @@ policies and contribution forms [3].
         // instanceof doesn't work if the node is from another window (like an
         // iframe's contentWindow):
         // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295
-        if ("nodeType" in object &&
-            "nodeName" in object &&
-            "nodeValue" in object &&
-            "childNodes" in object) {
+        try {
+            var has_node_properties = ("nodeType" in object &&
+                                       "nodeName" in object &&
+                                       "nodeValue" in object &&
+                                       "childNodes" in object);
+        } catch (e) {
+            // We're probably cross-origin, which means we aren't a node
+            return false;
+        }
+
+        if (has_node_properties) {
             try {
                 object.nodeType;
             } catch (e) {
@@ -553,6 +1041,44 @@ policies and contribution forms [3].
         return false;
     }
 
+    var replacements = {
+        "0": "0",
+        "1": "x01",
+        "2": "x02",
+        "3": "x03",
+        "4": "x04",
+        "5": "x05",
+        "6": "x06",
+        "7": "x07",
+        "8": "b",
+        "9": "t",
+        "10": "n",
+        "11": "v",
+        "12": "f",
+        "13": "r",
+        "14": "x0e",
+        "15": "x0f",
+        "16": "x10",
+        "17": "x11",
+        "18": "x12",
+        "19": "x13",
+        "20": "x14",
+        "21": "x15",
+        "22": "x16",
+        "23": "x17",
+        "24": "x18",
+        "25": "x19",
+        "26": "x1a",
+        "27": "x1b",
+        "28": "x1c",
+        "29": "x1d",
+        "30": "x1e",
+        "31": "x1f",
+        "0xfffd": "ufffd",
+        "0xfffe": "ufffe",
+        "0xffff": "uffff",
+    };
+
     /*
      * Convert a value to a nice, human-readable string
      */
@@ -568,49 +1094,23 @@ policies and contribution forms [3].
             seen.push(val);
         }
         if (Array.isArray(val)) {
-            return "[" + val.map(function(x) {return format_value(x, seen);}).join(", ") + "]";
+            let output = "[";
+            if (val.beginEllipsis !== undefined) {
+                output += "…, ";
+            }
+            output += val.map(function(x) {return format_value(x, seen);}).join(", ");
+            if (val.endEllipsis !== undefined) {
+                output += ", â€¦";
+            }
+            return output + "]";
         }
 
         switch (typeof val) {
         case "string":
-            val = val.replace("\\", "\\\\");
-            for (var i = 0; i < 32; i++) {
-                var replace = "\\";
-                switch (i) {
-                case 0: replace += "0"; break;
-                case 1: replace += "x01"; break;
-                case 2: replace += "x02"; break;
-                case 3: replace += "x03"; break;
-                case 4: replace += "x04"; break;
-                case 5: replace += "x05"; break;
-                case 6: replace += "x06"; break;
-                case 7: replace += "x07"; break;
-                case 8: replace += "b"; break;
-                case 9: replace += "t"; break;
-                case 10: replace += "n"; break;
-                case 11: replace += "v"; break;
-                case 12: replace += "f"; break;
-                case 13: replace += "r"; break;
-                case 14: replace += "x0e"; break;
-                case 15: replace += "x0f"; break;
-                case 16: replace += "x10"; break;
-                case 17: replace += "x11"; break;
-                case 18: replace += "x12"; break;
-                case 19: replace += "x13"; break;
-                case 20: replace += "x14"; break;
-                case 21: replace += "x15"; break;
-                case 22: replace += "x16"; break;
-                case 23: replace += "x17"; break;
-                case 24: replace += "x18"; break;
-                case 25: replace += "x19"; break;
-                case 26: replace += "x1a"; break;
-                case 27: replace += "x1b"; break;
-                case 28: replace += "x1c"; break;
-                case 29: replace += "x1d"; break;
-                case 30: replace += "x1e"; break;
-                case 31: replace += "x1f"; break;
-                }
-                val = val.replace(RegExp(String.fromCharCode(i), "g"), replace);
+            val = val.replace(/\\/g, "\\\\");
+            for (var p in replacements) {
+                var replace = "\\" + replacements[p];
+                val = val.replace(RegExp(String.fromCharCode(p), "g"), replace);
             }
             return '"' + val.replace(/"/g, '\\"') + '"';
         case "boolean":
@@ -658,7 +1158,12 @@ policies and contribution forms [3].
 
         /* falls through */
         default:
-            return typeof val + ' "' + truncate(String(val), 60) + '"';
+            try {
+                return typeof val + ' "' + truncate(String(val), 1000) + '"';
+            } catch(e) {
+                return ("[stringifying object threw " + String(e) +
+                        " with type " + String(typeof e) + "]");
+            }
         }
     }
     expose(format_value, "format_value");
@@ -667,19 +1172,53 @@ policies and contribution forms [3].
      * Assertions
      */
 
+    function expose_assert(f, name) {
+        function assert_wrapper(...args) {
+            let status = Test.statuses.TIMEOUT;
+            let stack = null;
+            try {
+                if (settings.debug) {
+                    console.debug("ASSERT", name, tests.current_test && tests.current_test.name, args);
+                }
+                if (tests.output) {
+                    tests.set_assert(name, args);
+                }
+                const rv = f.apply(undefined, args);
+                status = Test.statuses.PASS;
+                return rv;
+            } catch(e) {
+                if (e instanceof AssertionError) {
+                    status = Test.statuses.FAIL;
+                    stack = e.stack;
+                 } else {
+                    status = Test.statuses.ERROR;
+                 }
+                throw e;
+            } finally {
+                if (tests.output && !stack) {
+                    stack = get_stack();
+                }
+                if (tests.output) {
+                    tests.set_assert_status(status, stack);
+                }
+            }
+        }
+        expose(assert_wrapper, name);
+    }
+
     function assert_true(actual, description)
     {
         assert(actual === true, "assert_true", description,
                                 "expected true got ${actual}", {actual:actual});
     }
-    expose(assert_true, "assert_true");
+    expose_assert(assert_true, "assert_true");
 
     function assert_false(actual, description)
     {
         assert(actual === false, "assert_false", description,
                                  "expected false got ${actual}", {actual:actual});
     }
-    expose(assert_false, "assert_false");
+    expose_assert(assert_false, "assert_false");
 
     function same_value(x, y) {
         if (y !== y) {
@@ -709,7 +1248,7 @@ policies and contribution forms [3].
                                              "expected ${expected} but got ${actual}",
                                              {expected:expected, actual:actual});
     }
-    expose(assert_equals, "assert_equals");
+    expose_assert(assert_equals, "assert_equals");
 
     function assert_not_equals(actual, expected, description)
     {
@@ -721,7 +1260,7 @@ policies and contribution forms [3].
                                               "got disallowed value ${actual}",
                                               {actual:actual});
     }
-    expose(assert_not_equals, "assert_not_equals");
+    expose_assert(assert_not_equals, "assert_not_equals");
 
     function assert_in_array(actual, expected, description)
     {
@@ -729,10 +1268,15 @@ policies and contribution forms [3].
                                                "value ${actual} not in array ${expected}",
                                                {actual:actual, expected:expected});
     }
-    expose(assert_in_array, "assert_in_array");
+    expose_assert(assert_in_array, "assert_in_array");
 
+    // This function was deprecated in July of 2015.
+    // See https://github.com/web-platform-tests/wpt/issues/2033
     function assert_object_equals(actual, expected, description)
     {
+         assert(typeof actual === "object" && actual !== null, "assert_object_equals", description,
+                                                               "value is ${actual}, expected object",
+                                                               {actual: actual});
          //This needs to be improved a great deal
          function check_equal(actual, expected, stack)
          {
@@ -750,7 +1294,7 @@ policies and contribution forms [3].
                  } else {
                      assert(same_value(actual[p], expected[p]), "assert_object_equals", description,
                                                        "property ${p} expected ${expected} got ${actual}",
-                                                       {p:p, expected:expected, actual:actual});
+                                                       {p:p, expected:expected[p], actual:actual[p]});
                  }
              }
              for (p in expected) {
@@ -762,45 +1306,115 @@ policies and contribution forms [3].
          }
          check_equal(actual, expected, []);
     }
-    expose(assert_object_equals, "assert_object_equals");
+    expose_assert(assert_object_equals, "assert_object_equals");
 
     function assert_array_equals(actual, expected, description)
     {
+        const max_array_length = 20;
+        function shorten_array(arr, offset = 0) {
+            // Make ", â€¦" only show up when it would likely reduce the length, not accounting for
+            // fonts.
+            if (arr.length < max_array_length + 2) {
+                return arr;
+            }
+            // By default we want half the elements after the offset and half before
+            // But if that takes us past the end of the array, we have more before, and
+            // if it takes us before the start we have more after.
+            const length_after_offset = Math.floor(max_array_length / 2);
+            let upper_bound = Math.min(length_after_offset + offset, arr.length);
+            const lower_bound = Math.max(upper_bound - max_array_length, 0);
+
+            if (lower_bound === 0) {
+                upper_bound = max_array_length;
+            }
+
+            const output = arr.slice(lower_bound, upper_bound);
+            if (lower_bound > 0) {
+                output.beginEllipsis = true;
+            }
+            if (upper_bound < arr.length) {
+                output.endEllipsis = true;
+            }
+            return output;
+        }
+
+        assert(typeof actual === "object" && actual !== null && "length" in actual,
+               "assert_array_equals", description,
+               "value is ${actual}, expected array",
+               {actual:actual});
         assert(actual.length === expected.length,
                "assert_array_equals", description,
-               "lengths differ, expected ${expected} got ${actual}",
-               {expected:expected.length, actual:actual.length});
+               "lengths differ, expected array ${expected} length ${expectedLength}, got ${actual} length ${actualLength}",
+               {expected:shorten_array(expected, expected.length - 1), expectedLength:expected.length,
+                actual:shorten_array(actual, actual.length - 1), actualLength:actual.length
+               });
 
         for (var i = 0; i < actual.length; i++) {
             assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
                    "assert_array_equals", description,
-                   "property ${i}, property expected to be ${expected} but was ${actual}",
+                   "expected property ${i} to be ${expected} but was ${actual} (expected array ${arrayExpected} got ${arrayActual})",
                    {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
-                   actual:actual.hasOwnProperty(i) ? "present" : "missing"});
+                    actual:actual.hasOwnProperty(i) ? "present" : "missing",
+                    arrayExpected:shorten_array(expected, i), arrayActual:shorten_array(actual, i)});
             assert(same_value(expected[i], actual[i]),
                    "assert_array_equals", description,
-                   "property ${i}, expected ${expected} but got ${actual}",
-                   {i:i, expected:expected[i], actual:actual[i]});
+                   "expected property ${i} to be ${expected} but got ${actual} (expected array ${arrayExpected} got ${arrayActual})",
+                   {i:i, expected:expected[i], actual:actual[i],
+                    arrayExpected:shorten_array(expected, i), arrayActual:shorten_array(actual, i)});
         }
     }
-    expose(assert_array_equals, "assert_array_equals");
+    expose_assert(assert_array_equals, "assert_array_equals");
+
+    function assert_array_approx_equals(actual, expected, epsilon, description)
+    {
+        /*
+         * Test if two primitive arrays are equal within +/- epsilon
+         */
+        assert(actual.length === expected.length,
+               "assert_array_approx_equals", description,
+               "lengths differ, expected ${expected} got ${actual}",
+               {expected:expected.length, actual:actual.length});
+
+        for (var i = 0; i < actual.length; i++) {
+            assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
+                   "assert_array_approx_equals", description,
+                   "property ${i}, property expected to be ${expected} but was ${actual}",
+                   {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
+                    actual:actual.hasOwnProperty(i) ? "present" : "missing"});
+            assert(typeof actual[i] === "number",
+                   "assert_array_approx_equals", description,
+                   "property ${i}, expected a number but got a ${type_actual}",
+                   {i:i, type_actual:typeof actual[i]});
+            assert(Math.abs(actual[i] - expected[i]) <= epsilon,
+                   "assert_array_approx_equals", description,
+                   "property ${i}, expected ${expected} +/- ${epsilon}, expected ${expected} but got ${actual}",
+                   {i:i, expected:expected[i], actual:actual[i], epsilon:epsilon});
+        }
+    }
+    expose_assert(assert_array_approx_equals, "assert_array_approx_equals");
 
     function assert_approx_equals(actual, expected, epsilon, description)
     {
         /*
-         * Test if two primitive numbers are equal withing +/- epsilon
+         * Test if two primitive numbers are equal within +/- epsilon
          */
         assert(typeof actual === "number",
                "assert_approx_equals", description,
                "expected a number but got a ${type_actual}",
                {type_actual:typeof actual});
 
-        assert(Math.abs(actual - expected) <= epsilon,
-               "assert_approx_equals", description,
-               "expected ${expected} +/- ${epsilon} but got ${actual}",
-               {expected:expected, actual:actual, epsilon:epsilon});
+        // The epsilon math below does not place nice with NaN and Infinity
+        // But in this case Infinity = Infinity and NaN = NaN
+        if (isFinite(actual) || isFinite(expected)) {
+            assert(Math.abs(actual - expected) <= epsilon,
+                   "assert_approx_equals", description,
+                   "expected ${expected} +/- ${epsilon} but got ${actual}",
+                   {expected:expected, actual:actual, epsilon:epsilon});
+        } else {
+            assert_equals(actual, expected);
+        }
     }
-    expose(assert_approx_equals, "assert_approx_equals");
+    expose_assert(assert_approx_equals, "assert_approx_equals");
 
     function assert_less_than(actual, expected, description)
     {
@@ -817,7 +1431,7 @@ policies and contribution forms [3].
                "expected a number less than ${expected} but got ${actual}",
                {expected:expected, actual:actual});
     }
-    expose(assert_less_than, "assert_less_than");
+    expose_assert(assert_less_than, "assert_less_than");
 
     function assert_greater_than(actual, expected, description)
     {
@@ -834,7 +1448,25 @@ policies and contribution forms [3].
                "expected a number greater than ${expected} but got ${actual}",
                {expected:expected, actual:actual});
     }
-    expose(assert_greater_than, "assert_greater_than");
+    expose_assert(assert_greater_than, "assert_greater_than");
+
+    function assert_between_exclusive(actual, lower, upper, description)
+    {
+        /*
+         * Test if a primitive number is between two others
+         */
+        assert(typeof actual === "number",
+               "assert_between_exclusive", description,
+               "expected a number but got a ${type_actual}",
+               {type_actual:typeof actual});
+
+        assert(actual > lower && actual < upper,
+               "assert_between_exclusive", description,
+               "expected a number greater than ${lower} " +
+               "and less than ${upper} but got ${actual}",
+               {lower:lower, upper:upper, actual:actual});
+    }
+    expose_assert(assert_between_exclusive, "assert_between_exclusive");
 
     function assert_less_than_equal(actual, expected, description)
     {
@@ -847,11 +1479,11 @@ policies and contribution forms [3].
                {type_actual:typeof actual});
 
         assert(actual <= expected,
-               "assert_less_than", description,
+               "assert_less_than_equal", description,
                "expected a number less than or equal to ${expected} but got ${actual}",
                {expected:expected, actual:actual});
     }
-    expose(assert_less_than_equal, "assert_less_than_equal");
+    expose_assert(assert_less_than_equal, "assert_less_than_equal");
 
     function assert_greater_than_equal(actual, expected, description)
     {
@@ -868,7 +1500,25 @@ policies and contribution forms [3].
                "expected a number greater than or equal to ${expected} but got ${actual}",
                {expected:expected, actual:actual});
     }
-    expose(assert_greater_than_equal, "assert_greater_than_equal");
+    expose_assert(assert_greater_than_equal, "assert_greater_than_equal");
+
+    function assert_between_inclusive(actual, lower, upper, description)
+    {
+        /*
+         * Test if a primitive number is between to two others or equal to either of them
+         */
+        assert(typeof actual === "number",
+               "assert_between_inclusive", description,
+               "expected a number but got a ${type_actual}",
+               {type_actual:typeof actual});
+
+        assert(actual >= lower && actual <= upper,
+               "assert_between_inclusive", description,
+               "expected a number greater than or equal to ${lower} " +
+               "and less than or equal to ${upper} but got ${actual}",
+               {lower:lower, upper:upper, actual:actual});
+    }
+    expose_assert(assert_between_inclusive, "assert_between_inclusive");
 
     function assert_regexp_match(actual, expected, description) {
         /*
@@ -879,38 +1529,38 @@ policies and contribution forms [3].
                "expected ${expected} but got ${actual}",
                {expected:expected, actual:actual});
     }
-    expose(assert_regexp_match, "assert_regexp_match");
+    expose_assert(assert_regexp_match, "assert_regexp_match");
 
     function assert_class_string(object, class_string, description) {
-        assert_equals({}.toString.call(object), "[object " + class_string + "]",
-                      description);
+        var actual = {}.toString.call(object);
+        var expected = "[object " + class_string + "]";
+        assert(same_value(actual, expected), "assert_class_string", description,
+                                             "expected ${expected} but got ${actual}",
+                                             {expected:expected, actual:actual});
     }
-    expose(assert_class_string, "assert_class_string");
+    expose_assert(assert_class_string, "assert_class_string");
 
-
-    function _assert_own_property(name) {
-        return function(object, property_name, description)
-        {
-            assert(property_name in object,
-                   name, description,
-                   "expected property ${p} missing", {p:property_name});
-        };
+    function assert_own_property(object, property_name, description) {
+        assert(object.hasOwnProperty(property_name),
+               "assert_own_property", description,
+               "expected property ${p} missing", {p:property_name});
     }
-    expose(_assert_own_property("assert_exists"), "assert_exists");
-    expose(_assert_own_property("assert_own_property"), "assert_own_property");
+    expose_assert(assert_own_property, "assert_own_property");
 
-    function assert_not_exists(object, property_name, description)
-    {
+    function assert_not_own_property(object, property_name, description) {
         assert(!object.hasOwnProperty(property_name),
-               "assert_not_exists", description,
-               "unexpected property ${p} found", {p:property_name});
+               "assert_not_own_property", description,
+               "unexpected property ${p} is found on object", {p:property_name});
     }
-    expose(assert_not_exists, "assert_not_exists");
+    expose_assert(assert_not_own_property, "assert_not_own_property");
 
     function _assert_inherits(name) {
         return function (object, property_name, description)
         {
-            assert(typeof object === "object",
+            assert((typeof object === "object" && object !== null) ||
+                   typeof object === "function" ||
+                   // Or has [[IsHTMLDDA]] slot
+                   String(object) === "[object HTMLAllCollection]",
                    name, description,
                    "provided value is not an object");
 
@@ -929,8 +1579,8 @@ policies and contribution forms [3].
                    {p:property_name});
         };
     }
-    expose(_assert_inherits("assert_inherits"), "assert_inherits");
-    expose(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");
+    expose_assert(_assert_inherits("assert_inherits"), "assert_inherits");
+    expose_assert(_assert_inherits("assert_idl_attribute"), "assert_idl_attribute");
 
     function assert_readonly(object, property_name, description)
     {
@@ -947,32 +1597,156 @@ policies and contribution forms [3].
              object[property_name] = initial_value;
          }
     }
-    expose(assert_readonly, "assert_readonly");
+    expose_assert(assert_readonly, "assert_readonly");
+
+    /**
+     * Assert a JS Error with the expected constructor is thrown.
+     *
+     * @param {object} constructor The expected exception constructor.
+     * @param {Function} func Function which should throw.
+     * @param {string} description Error description for the case that the error is not thrown.
+     */
+    function assert_throws_js(constructor, func, description)
+    {
+        assert_throws_js_impl(constructor, func, description,
+                              "assert_throws_js");
+    }
+    expose_assert(assert_throws_js, "assert_throws_js");
 
-    function assert_throws(code, func, description)
+    /**
+     * Like assert_throws_js but allows specifying the assertion type
+     * (assert_throws_js or promise_rejects_js, in practice).
+     */
+    function assert_throws_js_impl(constructor, func, description,
+                                   assertion_type)
     {
         try {
             func.call(this);
-            assert(false, "assert_throws", description,
+            assert(false, assertion_type, description,
                    "${func} did not throw", {func:func});
         } catch (e) {
             if (e instanceof AssertionError) {
                 throw e;
             }
-            if (code === null) {
-                return;
+
+            // Basic sanity-checks on the thrown exception.
+            assert(typeof e === "object",
+                   assertion_type, description,
+                   "${func} threw ${e} with type ${type}, not an object",
+                   {func:func, e:e, type:typeof e});
+
+            assert(e !== null,
+                   assertion_type, description,
+                   "${func} threw null, not an object",
+                   {func:func});
+
+            // Basic sanity-check on the passed-in constructor
+            assert(typeof constructor == "function",
+                   assertion_type, description,
+                   "${constructor} is not a constructor",
+                   {constructor:constructor});
+            var obj = constructor;
+            while (obj) {
+                if (typeof obj === "function" &&
+                    obj.name === "Error") {
+                    break;
+                }
+                obj = Object.getPrototypeOf(obj);
             }
-            if (typeof code === "object") {
-                assert(typeof e == "object" && "name" in e && e.name == code.name,
-                       "assert_throws", description,
-                       "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",
-                                    {func:func, actual:e, actual_name:e.name,
-                                     expected:code,
-                                     expected_name:code.name});
-                return;
+            assert(obj != null,
+                   assertion_type, description,
+                   "${constructor} is not an Error subtype",
+                   {constructor:constructor});
+
+            // And checking that our exception is reasonable
+            assert(e.constructor === constructor &&
+                   e.name === constructor.name,
+                   assertion_type, description,
+                   "${func} threw ${actual} (${actual_name}) expected instance of ${expected} (${expected_name})",
+                   {func:func, actual:e, actual_name:e.name,
+                    expected:constructor,
+                    expected_name:constructor.name});
+        }
+    }
+
+    /**
+     * Assert a DOMException with the expected type is thrown.
+     *
+     * @param {number|string} type The expected exception name or code.  See the
+     *        table of names and codes at
+     *        https://heycam.github.io/webidl/#dfn-error-names-table
+     *        If a number is passed it should be one of the numeric code values
+     *        in that table (e.g. 3, 4, etc).  If a string is passed it can
+     *        either be an exception name (e.g. "HierarchyRequestError",
+     *        "WrongDocumentError") or the name of the corresponding error code
+     *        (e.g. "HIERARCHY_REQUEST_ERR", "WRONG_DOCUMENT_ERR").
+     *
+     * For the remaining arguments, there are two ways of calling
+     * promise_rejects_dom:
+     *
+     * 1) If the DOMException is expected to come from the current global, the
+     * second argument should be the function expected to throw and a third,
+     * optional, argument is the assertion description.
+     *
+     * 2) If the DOMException is expected to come from some other global, the
+     * second argument should be the DOMException constructor from that global,
+     * the third argument the function expected to throw, and the fourth, optional,
+     * argument the assertion description.
+     */
+    function assert_throws_dom(type, funcOrConstructor, descriptionOrFunc, maybeDescription)
+    {
+        let constructor, func, description;
+        if (funcOrConstructor.name === "DOMException") {
+            constructor = funcOrConstructor;
+            func = descriptionOrFunc;
+            description = maybeDescription;
+        } else {
+            constructor = self.DOMException;
+            func = funcOrConstructor;
+            description = descriptionOrFunc;
+            assert(maybeDescription === undefined,
+                   "Too many args pased to no-constructor version of assert_throws_dom");
+        }
+        assert_throws_dom_impl(type, func, description, "assert_throws_dom", constructor)
+    }
+    expose_assert(assert_throws_dom, "assert_throws_dom");
+
+    /**
+     * Similar to assert_throws_dom but allows specifying the assertion type
+     * (assert_throws_dom or promise_rejects_dom, in practice).  The
+     * "constructor" argument must be the DOMException constructor from the
+     * global we expect the exception to come from.
+     */
+    function assert_throws_dom_impl(type, func, description, assertion_type, constructor)
+    {
+        try {
+            func.call(this);
+            assert(false, assertion_type, description,
+                   "${func} did not throw", {func:func});
+        } catch (e) {
+            if (e instanceof AssertionError) {
+                throw e;
             }
 
-            var code_name_map = {
+            // Basic sanity-checks on the thrown exception.
+            assert(typeof e === "object",
+                   assertion_type, description,
+                   "${func} threw ${e} with type ${type}, not an object",
+                   {func:func, e:e, type:typeof e});
+
+            assert(e !== null,
+                   assertion_type, description,
+                   "${func} threw null, not an object",
+                   {func:func});
+
+            // Sanity-check our type
+            assert(typeof type == "number" ||
+                   typeof type == "string",
+                   assertion_type, description,
+                   "${type} is not a number or string",
+                   {type:type});
+
+            var codename_name_map = {
                 INDEX_SIZE_ERR: 'IndexSizeError',
                 HIERARCHY_REQUEST_ERR: 'HierarchyRequestError',
                 WRONG_DOCUMENT_ERR: 'WrongDocumentError',
@@ -980,6 +1754,7 @@ policies and contribution forms [3].
                 NO_MODIFICATION_ALLOWED_ERR: 'NoModificationAllowedError',
                 NOT_FOUND_ERR: 'NotFoundError',
                 NOT_SUPPORTED_ERR: 'NotSupportedError',
+                INUSE_ATTRIBUTE_ERR: 'InUseAttributeError',
                 INVALID_STATE_ERR: 'InvalidStateError',
                 SYNTAX_ERR: 'SyntaxError',
                 INVALID_MODIFICATION_ERR: 'InvalidModificationError',
@@ -996,8 +1771,6 @@ policies and contribution forms [3].
                 DATA_CLONE_ERR: 'DataCloneError'
             };
 
-            var name = code in code_name_map ? code_name_map[code] : code;
-
             var name_code_map = {
                 IndexSizeError: 1,
                 HierarchyRequestError: 3,
@@ -1006,6 +1779,7 @@ policies and contribution forms [3].
                 NoModificationAllowedError: 7,
                 NotFoundError: 8,
                 NotSupportedError: 9,
+                InUseAttributeError: 10,
                 InvalidStateError: 11,
                 SyntaxError: 12,
                 InvalidModificationError: 13,
@@ -1021,51 +1795,112 @@ policies and contribution forms [3].
                 InvalidNodeTypeError: 24,
                 DataCloneError: 25,
 
+                EncodingError: 0,
+                NotReadableError: 0,
                 UnknownError: 0,
                 ConstraintError: 0,
                 DataError: 0,
                 TransactionInactiveError: 0,
                 ReadOnlyError: 0,
-                VersionError: 0
+                VersionError: 0,
+                OperationError: 0,
+                NotAllowedError: 0
             };
 
-            if (!(name in name_code_map)) {
-                throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()');
+            var code_name_map = {};
+            for (var key in name_code_map) {
+                if (name_code_map[key] > 0) {
+                    code_name_map[name_code_map[key]] = key;
+                }
             }
 
-            var required_props = { code: name_code_map[name] };
+            var required_props = {};
+            var name;
+
+            if (typeof type === "number") {
+                if (type === 0) {
+                    throw new AssertionError('Test bug: ambiguous DOMException code 0 passed to assert_throws_dom()');
+                } else if (!(type in code_name_map)) {
+                    throw new AssertionError('Test bug: unrecognized DOMException code "' + type + '" passed to assert_throws_dom()');
+                }
+                name = code_name_map[type];
+                required_props.code = type;
+            } else if (typeof type === "string") {
+                name = type in codename_name_map ? codename_name_map[type] : type;
+                if (!(name in name_code_map)) {
+                    throw new AssertionError('Test bug: unrecognized DOMException code name or name "' + type + '" passed to assert_throws_dom()');
+                }
+
+                required_props.code = name_code_map[name];
+            }
 
             if (required_props.code === 0 ||
-               ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DOMException")) {
+               ("name" in e &&
+                e.name !== e.name.toUpperCase() &&
+                e.name !== "DOMException")) {
                 // New style exception: also test the name property.
                 required_props.name = name;
             }
 
-            //We'd like to test that e instanceof the appropriate interface,
-            //but we can't, because we don't know what window it was created
-            //in.  It might be an instanceof the appropriate interface on some
-            //unknown other window.  TODO: Work around this somehow?
-
-            assert(typeof e == "object",
-                   "assert_throws", description,
-                   "${func} threw ${e} with type ${type}, not an object",
-                   {func:func, e:e, type:typeof e});
-
             for (var prop in required_props) {
-                assert(typeof e == "object" && prop in e && e[prop] == required_props[prop],
-                       "assert_throws", description,
-                       "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",
+                assert(prop in e && e[prop] == required_props[prop],
+                       assertion_type, description,
+                       "${func} threw ${e} that is not a DOMException " + type + ": property ${prop} is equal to ${actual}, expected ${expected}",
                        {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
             }
+
+            // Check that the exception is from the right global.  This check is last
+            // so more specific, and more informative, checks on the properties can
+            // happen in case a totally incorrect exception is thrown.
+            assert(e.constructor === constructor,
+                   assertion_type, description,
+                   "${func} threw an exception from the wrong global",
+                   {func});
+
+        }
+    }
+
+    /**
+     * Assert the provided value is thrown.
+     *
+     * @param {value} exception The expected exception.
+     * @param {Function} func Function which should throw.
+     * @param {string} description Error description for the case that the error is not thrown.
+     */
+    function assert_throws_exactly(exception, func, description)
+    {
+        assert_throws_exactly_impl(exception, func, description,
+                                   "assert_throws_exactly");
+    }
+    expose_assert(assert_throws_exactly, "assert_throws_exactly");
+
+    /**
+     * Like assert_throws_exactly but allows specifying the assertion type
+     * (assert_throws_exactly or promise_rejects_exactly, in practice).
+     */
+    function assert_throws_exactly_impl(exception, func, description,
+                                        assertion_type)
+    {
+        try {
+            func.call(this);
+            assert(false, assertion_type, description,
+                   "${func} did not throw", {func:func});
+        } catch (e) {
+            if (e instanceof AssertionError) {
+                throw e;
+            }
+
+            assert(same_value(e, exception), assertion_type, description,
+                   "${func} threw ${e} but we expected it to throw ${exception}",
+                   {func:func, e:e, exception:exception});
         }
     }
-    expose(assert_throws, "assert_throws");
 
     function assert_unreached(description) {
          assert(false, "assert_unreached", description,
                 "Reached unreachable code");
     }
-    expose(assert_unreached, "assert_unreached");
+    expose_assert(assert_unreached, "assert_unreached");
 
     function assert_any(assert_func, actual, expected_array)
     {
@@ -1086,8 +1921,48 @@ policies and contribution forms [3].
             throw new AssertionError(errors.join("\n\n"));
         }
     }
+    // FIXME: assert_any cannot use expose_assert, because assert_wrapper does
+    // not support nested assert calls (e.g. to assert_func). We need to
+    // support bypassing assert_wrapper for the inner asserts here.
     expose(assert_any, "assert_any");
 
+    /**
+     * Assert that a feature is implemented, based on a 'truthy' condition.
+     *
+     * This function should be used to early-exit from tests in which there is
+     * no point continuing without support for a non-optional spec or spec
+     * feature. For example:
+     *
+     *     assert_implements(window.Foo, 'Foo is not supported');
+     *
+     * @param {object} condition The truthy value to test
+     * @param {string} description Error description for the case that the condition is not truthy.
+     */
+    function assert_implements(condition, description) {
+        assert(!!condition, "assert_implements", description);
+    }
+    expose_assert(assert_implements, "assert_implements")
+
+    /**
+     * Assert that an optional feature is implemented, based on a 'truthy' condition.
+     *
+     * This function should be used to early-exit from tests in which there is
+     * no point continuing without support for an explicitly optional spec or
+     * spec feature. For example:
+     *
+     *     assert_implements_optional(video.canPlayType("video/webm"),
+     *                                "webm video playback not supported");
+     *
+     * @param {object} condition The truthy value to test
+     * @param {string} description Error description for the case that the condition is not truthy.
+     */
+    function assert_implements_optional(condition, description) {
+        if (!condition) {
+            throw new OptionalFeatureUnsupportedError(description);
+        }
+    }
+    expose_assert(assert_implements_optional, "assert_implements_optional")
+
     function Test(name, properties)
     {
         if (tests.file_is_test && tests.tests.length) {
@@ -1095,25 +1970,35 @@ policies and contribution forms [3].
         }
         this.name = name;
 
-        this.phase = this.phases.INITIAL;
+        this.phase = (tests.is_aborted || tests.phase === tests.phases.COMPLETE) ?
+            this.phases.COMPLETE : this.phases.INITIAL;
 
         this.status = this.NOTRUN;
         this.timeout_id = null;
         this.index = null;
 
-        this.properties = properties;
-        var timeout = properties.timeout ? properties.timeout : settings.test_timeout;
-        if (timeout !== null) {
-            this.timeout_length = timeout * tests.timeout_multiplier;
-        } else {
-            this.timeout_length = null;
+        this.properties = properties || {};
+        this.timeout_length = settings.test_timeout;
+        if (this.timeout_length !== null) {
+            this.timeout_length *= tests.timeout_multiplier;
         }
 
         this.message = null;
+        this.stack = null;
 
         this.steps = [];
+        this._is_promise_test = false;
 
         this.cleanup_callbacks = [];
+        this._user_defined_cleanup_count = 0;
+        this._done_callbacks = [];
+
+        // Tests declared following harness completion are likely an indication
+        // of a programming error, but they cannot be reported
+        // deterministically.
+        if (tests.phase === tests.phases.COMPLETE) {
+            return;
+        }
 
         tests.push(this);
     }
@@ -1122,7 +2007,8 @@ policies and contribution forms [3].
         PASS:0,
         FAIL:1,
         TIMEOUT:2,
-        NOTRUN:3
+        NOTRUN:3,
+        PRECONDITION_FAILED:4
     };
 
     Test.prototype = merge({}, Test.statuses);
@@ -1131,9 +2017,22 @@ policies and contribution forms [3].
         INITIAL:0,
         STARTED:1,
         HAS_RESULT:2,
-        COMPLETE:3
+        CLEANING:3,
+        COMPLETE:4
     };
 
+    Test.prototype.status_formats = {
+        0: "Pass",
+        1: "Fail",
+        2: "Timeout",
+        3: "Not Run",
+        4: "Optional Feature Unsupported",
+    }
+
+    Test.prototype.format_status = function() {
+        return this.status_formats[this.status];
+    }
+
     Test.prototype.structured_clone = function()
     {
         if (!this._structured_clone) {
@@ -1142,11 +2041,14 @@ policies and contribution forms [3].
             this._structured_clone = merge({
                 name:String(this.name),
                 properties:merge({}, this.properties),
+                phases:merge({}, this.phases)
             }, Test.statuses);
         }
         this._structured_clone.status = this.status;
         this._structured_clone.message = this.message;
+        this._structured_clone.stack = this.stack;
         this._structured_clone.index = this.index;
+        this._structured_clone.phase = this.phase;
         return this._structured_clone;
     };
 
@@ -1155,11 +2057,16 @@ policies and contribution forms [3].
         if (this.phase > this.phases.STARTED) {
             return;
         }
+
+        if (settings.debug && this.phase !== this.phases.STARTED) {
+            console.log("TEST START", this.name);
+        }
         this.phase = this.phases.STARTED;
-        //If we don't get a result before the harness times out that will be a test timout
+        //If we don't get a result before the harness times out that will be a test timeout
         this.set_status(this.TIMEOUT, "Test timed out");
 
         tests.started = true;
+        tests.current_test = this;
         tests.notify_test_state(this);
 
         if (this.timeout_id === null) {
@@ -1172,23 +2079,25 @@ policies and contribution forms [3].
             this_obj = this;
         }
 
+        if (settings.debug) {
+            console.debug("TEST STEP", this.name);
+        }
+
         try {
             return func.apply(this_obj, Array.prototype.slice.call(arguments, 2));
         } catch (e) {
             if (this.phase >= this.phases.HAS_RESULT) {
                 return;
             }
-            var message = (typeof e === "object" && e !== null) ? e.message : e;
-            if (typeof e.stack != "undefined" && typeof e.message == "string") {
-                //Try to make it more informative for some exceptions, at least
-                //in Gecko and WebKit.  This results in a stack dump instead of
-                //just errors like "Cannot read property 'parentNode' of null"
-                //or "root is null".  Makes it a lot longer, of course.
-                message += "(stack: " + e.stack + ")";
-            }
-            this.set_status(this.FAIL, message);
+            var status = e instanceof OptionalFeatureUnsupportedError ? this.PRECONDITION_FAILED : this.FAIL;
+            var message = String((typeof e === "object" && e !== null) ? e.message : e);
+            var stack = e.stack ? e.stack : null;
+
+            this.set_status(status, message, stack);
             this.phase = this.phases.HAS_RESULT;
             this.done();
+        } finally {
+            this.current_test = null;
         }
     };
 
@@ -1232,13 +2141,132 @@ policies and contribution forms [3].
         });
     };
 
-    Test.prototype.add_cleanup = function(callback) {
+    Test.prototype.step_timeout = function(f, timeout) {
+        var test_this = this;
+        var args = Array.prototype.slice.call(arguments, 2);
+        return setTimeout(this.step_func(function() {
+            return f.apply(test_this, args);
+        }), timeout * tests.timeout_multiplier);
+    };
+
+    Test.prototype.step_wait_func = function(cond, func, description,
+                                             timeout=3000, interval=100) {
+        /**
+         * Poll for a function to return true, and call a callback
+         * function once it does, or assert if a timeout is
+         * reached. This is preferred over a simple step_timeout
+         * whenever possible since it allows the timeout to be longer
+         * to reduce intermittents without compromising test execution
+         * speed when the condition is quickly met.
+         *
+         * @param {Function} cond A function taking no arguments and
+         *                        returning a boolean. The callback is called
+         *                        when this function returns true.
+         * @param {Function} func A function taking no arguments to call once
+         *                        the condition is met.
+         * @param {string} description Error message to add to assert in case of
+         *                             failure.
+         * @param {number} timeout Timeout in ms. This is multiplied by the global
+         *                         timeout_multiplier
+         * @param {number} interval Polling interval in ms
+         *
+         **/
+
+        var timeout_full = timeout * tests.timeout_multiplier;
+        var remaining = Math.ceil(timeout_full / interval);
+        var test_this = this;
+
+        var wait_for_inner = test_this.step_func(() => {
+            if (cond()) {
+                func();
+            } else {
+                if(remaining === 0) {
+                    assert(false, "step_wait_func", description,
+                           "Timed out waiting on condition");
+                }
+                remaining--;
+                setTimeout(wait_for_inner, interval);
+            }
+        });
+
+        wait_for_inner();
+    };
+
+    Test.prototype.step_wait_func_done = function(cond, func, description,
+                                                  timeout=3000, interval=100) {
+        /**
+         * Poll for a function to return true, and invoke a callback
+         * followed by this.done() once it does, or assert if a timeout
+         * is reached. This is preferred over a simple step_timeout
+         * whenever possible since it allows the timeout to be longer
+         * to reduce intermittents without compromising test execution speed
+         * when the condition is quickly met.
+         *
+         * @param {Function} cond A function taking no arguments and
+         *                        returning a boolean. The callback is called
+         *                        when this function returns true.
+         * @param {Function} func A function taking no arguments to call once
+         *                        the condition is met.
+         * @param {string} description Error message to add to assert in case of
+         *                             failure.
+         * @param {number} timeout Timeout in ms. This is multiplied by the global
+         *                         timeout_multiplier
+         * @param {number} interval Polling interval in ms
+         *
+         **/
+
+         this.step_wait_func(cond, () => {
+            if (func) {
+                func();
+            }
+            this.done();
+         }, description, timeout, interval);
+    }
+
+    Test.prototype.step_wait = function(cond, description, timeout=3000, interval=100) {
+        /**
+         * Poll for a function to return true, and resolve a promise
+         * once it does, or assert if a timeout is reached. This is
+         * preferred over a simple step_timeout whenever possible
+         * since it allows the timeout to be longer to reduce
+         * intermittents without compromising test execution speed
+         * when the condition is quickly met.
+         *
+         * @param {Function} cond A function taking no arguments and
+         *                        returning a boolean.
+         * @param {string} description Error message to add to assert in case of
+         *                             failure.
+         * @param {number} timeout Timeout in ms. This is multiplied by the global
+         *                         timeout_multiplier
+         * @param {number} interval Polling interval in ms
+         * @returns {Promise} Promise resolved once cond is met.
+         *
+         **/
+
+        return new Promise(resolve => {
+            this.step_wait_func(cond, resolve, description, timeout, interval);
+        });
+    }
+
+    /*
+     * Private method for registering cleanup functions. `testharness.js`
+     * internals should use this method instead of the public `add_cleanup`
+     * method in order to hide implementation details from the harness status
+     * message in the case errors.
+     */
+    Test.prototype._add_cleanup = function(callback) {
         this.cleanup_callbacks.push(callback);
     };
 
-    Test.prototype.force_timeout = function() {
-        this.set_status(this.TIMEOUT);
-        this.phase = this.phases.HAS_RESULT;
+    /*
+     * Schedule a function to be run after the test result is known, regardless
+     * of passing or failing state. The behavior of this function will not
+     * influence the result of the test, but if an exception is thrown, the
+     * test harness will report an error.
+     */
+    Test.prototype.add_cleanup = function(callback) {
+        this._user_defined_cleanup_count += 1;
+        this._add_cleanup(callback);
     };
 
     Test.prototype.set_timeout = function()
@@ -1252,10 +2280,11 @@ policies and contribution forms [3].
         }
     };
 
-    Test.prototype.set_status = function(status, message)
+    Test.prototype.set_status = function(status, message, stack)
     {
         this.status = status;
         this.message = message;
+        this.stack = stack ? stack : null;
     };
 
     Test.prototype.timeout = function()
@@ -1266,9 +2295,15 @@ policies and contribution forms [3].
         this.done();
     };
 
+    Test.prototype.force_timeout = Test.prototype.timeout;
+
+    /**
+     * Update the test status, initiate "cleanup" functions, and signal test
+     * completion.
+     */
     Test.prototype.done = function()
     {
-        if (this.phase == this.phases.COMPLETE) {
+        if (this.phase >= this.phases.CLEANING) {
             return;
         }
 
@@ -1276,20 +2311,139 @@ policies and contribution forms [3].
             this.set_status(this.PASS, null);
         }
 
-        this.phase = this.phases.COMPLETE;
+        if (global_scope.clearTimeout) {
+            clearTimeout(this.timeout_id);
+        }
+
+        if (settings.debug) {
+            console.log("TEST DONE",
+                        this.status,
+                        this.name,)
+        }
 
-        clearTimeout(this.timeout_id);
-        tests.result(this);
         this.cleanup();
     };
 
+    function add_test_done_callback(test, callback)
+    {
+        if (test.phase === test.phases.COMPLETE) {
+            callback();
+            return;
+        }
+
+        test._done_callbacks.push(callback);
+    }
+
+    /*
+     * Invoke all specified cleanup functions. If one or more produce an error,
+     * the context is in an unpredictable state, so all further testing should
+     * be cancelled.
+     */
     Test.prototype.cleanup = function() {
+        var error_count = 0;
+        var bad_value_count = 0;
+        function on_error() {
+            error_count += 1;
+            // Abort tests immediately so that tests declared within subsequent
+            // cleanup functions are not run.
+            tests.abort();
+        }
+        var this_obj = this;
+        var results = [];
+
+        this.phase = this.phases.CLEANING;
+
         forEach(this.cleanup_callbacks,
                 function(cleanup_callback) {
-                    cleanup_callback();
+                    var result;
+
+                    try {
+                        result = cleanup_callback();
+                    } catch (e) {
+                        on_error();
+                        return;
+                    }
+
+                    if (!is_valid_cleanup_result(this_obj, result)) {
+                        bad_value_count += 1;
+                        // Abort tests immediately so that tests declared
+                        // within subsequent cleanup functions are not run.
+                        tests.abort();
+                    }
+
+                    results.push(result);
                 });
+
+        if (!this._is_promise_test) {
+            cleanup_done(this_obj, error_count, bad_value_count);
+        } else {
+            all_async(results,
+                      function(result, done) {
+                          if (result && typeof result.then === "function") {
+                              result
+                                  .then(null, on_error)
+                                  .then(done);
+                          } else {
+                              done();
+                          }
+                      },
+                      function() {
+                          cleanup_done(this_obj, error_count, bad_value_count);
+                      });
+        }
     };
 
+    /**
+     * Determine if the return value of a cleanup function is valid for a given
+     * test. Any test may return the value `undefined`. Tests created with
+     * `promise_test` may alternatively return "thenable" object values.
+     */
+    function is_valid_cleanup_result(test, result) {
+        if (result === undefined) {
+            return true;
+        }
+
+        if (test._is_promise_test) {
+            return result && typeof result.then === "function";
+        }
+
+        return false;
+    }
+
+    function cleanup_done(test, error_count, bad_value_count) {
+        if (error_count || bad_value_count) {
+            var total = test._user_defined_cleanup_count;
+
+            tests.status.status = tests.status.ERROR;
+            tests.status.message = "Test named '" + test.name +
+                "' specified " + total +
+                " 'cleanup' function" + (total > 1 ? "s" : "");
+
+            if (error_count) {
+                tests.status.message += ", and " + error_count + " failed";
+            }
+
+            if (bad_value_count) {
+                var type = test._is_promise_test ?
+                   "non-thenable" : "non-undefined";
+                tests.status.message += ", and " + bad_value_count +
+                    " returned a " + type + " value";
+            }
+
+            tests.status.message += ".";
+
+            tests.status.stack = null;
+        }
+
+        test.phase = test.phases.COMPLETE;
+        tests.result(test);
+        forEach(test._done_callbacks,
+                function(callback) {
+                    callback();
+                });
+        test._done_callbacks.length = 0;
+    }
+
     /*
      * A RemoteTest object mirrors a Test object on a remote worker. The
      * associated RemoteWorker updates the RemoteTest object in response to
@@ -1306,94 +2460,156 @@ policies and contribution forms [3].
         this.index = null;
         this.phase = this.phases.INITIAL;
         this.update_state_from(clone);
+        this._done_callbacks = [];
         tests.push(this);
     }
 
     RemoteTest.prototype.structured_clone = function() {
         var clone = {};
         Object.keys(this).forEach(
-                function(key) {
-                    if (typeof(this[key]) === "object") {
-                        clone[key] = merge({}, this[key]);
+                (function(key) {
+                    var value = this[key];
+                    // `RemoteTest` instances are responsible for managing
+                    // their own "done" callback functions, so those functions
+                    // are not relevant in other execution contexts. Because of
+                    // this (and because Function values cannot be serialized
+                    // for cross-realm transmittance), the property should not
+                    // be considered when cloning instances.
+                    if (key === '_done_callbacks' ) {
+                        return;
+                    }
+
+                    if (typeof value === "object" && value !== null) {
+                        clone[key] = merge({}, value);
                     } else {
-                        clone[key] = this[key];
+                        clone[key] = value;
                     }
-                });
+                }).bind(this));
         clone.phases = merge({}, this.phases);
         return clone;
     };
 
-    RemoteTest.prototype.cleanup = function() {};
+    /**
+     * `RemoteTest` instances are objects which represent tests running in
+     * another realm. They do not define "cleanup" functions (if necessary,
+     * such functions are defined on the associated `Test` instance within the
+     * external realm). However, `RemoteTests` may have "done" callbacks (e.g.
+     * as attached by the `Tests` instance responsible for tracking the overall
+     * test status in the parent realm). The `cleanup` method delegates to
+     * `done` in order to ensure that such callbacks are invoked following the
+     * completion of the `RemoteTest`.
+     */
+    RemoteTest.prototype.cleanup = function() {
+        this.done();
+    };
     RemoteTest.prototype.phases = Test.prototype.phases;
     RemoteTest.prototype.update_state_from = function(clone) {
         this.status = clone.status;
         this.message = clone.message;
+        this.stack = clone.stack;
         if (this.phase === this.phases.INITIAL) {
             this.phase = this.phases.STARTED;
         }
     };
     RemoteTest.prototype.done = function() {
         this.phase = this.phases.COMPLETE;
+
+        forEach(this._done_callbacks,
+                function(callback) {
+                    callback();
+                });
+    }
+
+    RemoteTest.prototype.format_status = function() {
+        return Test.prototype.status_formats[this.status];
     }
 
     /*
-     * A RemoteWorker listens for test events from a worker. These events are
-     * then used to construct and maintain RemoteTest objects that mirror the
-     * tests running on the remote worker.
+     * A RemoteContext listens for test events from a remote test context, such
+     * as another window or a worker. These events are then used to construct
+     * and maintain RemoteTest objects that mirror the tests running in the
+     * remote context.
+     *
+     * An optional third parameter can be used as a predicate to filter incoming
+     * MessageEvents.
      */
-    function RemoteWorker(worker) {
+    function RemoteContext(remote, message_target, message_filter) {
         this.running = true;
+        this.started = false;
         this.tests = new Array();
+        this.early_exception = null;
 
         var this_obj = this;
-        worker.onerror = function(error) { this_obj.worker_error(error); };
+        // If remote context is cross origin assigning to onerror is not
+        // possible, so silently catch those errors.
+        try {
+          remote.onerror = function(error) { this_obj.remote_error(error); };
+        } catch (e) {
+          // Ignore.
+        }
 
-        var message_port;
+        // Keeping a reference to the remote object and the message handler until
+        // remote_done() is seen prevents the remote object and its message channel
+        // from going away before all the messages are dispatched.
+        this.remote = remote;
+        this.message_target = message_target;
+        this.message_handler = function(message) {
+            var passesFilter = !message_filter || message_filter(message);
+            // The reference to the `running` property in the following
+            // condition is unnecessary because that value is only set to
+            // `false` after the `message_handler` function has been
+            // unsubscribed.
+            // TODO: Simplify the condition by removing the reference.
+            if (this_obj.running && message.data && passesFilter &&
+                (message.data.type in this_obj.message_handlers)) {
+                this_obj.message_handlers[message.data.type].call(this_obj, message.data);
+            }
+        };
 
-        if (is_service_worker(worker)) {
-            // The ServiceWorker's implicit MessagePort is currently not
-            // reliably accessible from the ServiceWorkerGlobalScope due to
-            // Blink setting MessageEvent.source to null for messages sent via
-            // ServiceWorker.postMessage(). Until that's resolved, create an
-            // explicit MessageChannel and pass one end to the worker.
-            var message_channel = new MessageChannel();
-            message_port = message_channel.port1;
-            message_port.start();
-            worker.postMessage({type: "connect"}, [message_channel.port2]);
-        } else if (is_shared_worker(worker)) {
-            message_port = worker.port;
-        } else {
-            message_port = worker;
+        if (self.Promise) {
+            this.done = new Promise(function(resolve) {
+                this_obj.doneResolve = resolve;
+            });
         }
 
-        // Keeping a reference to the worker until worker_done() is seen
-        // prevents the Worker object and its MessageChannel from going away
-        // before all the messages are dispatched.
-        this.worker = worker;
-
-        message_port.onmessage =
-            function(message) {
-                if (this_obj.running && (message.data.type in this_obj.message_handlers)) {
-                    this_obj.message_handlers[message.data.type].call(this_obj, message.data);
-                }
-            };
+        this.message_target.addEventListener("message", this.message_handler);
     }
 
-    RemoteWorker.prototype.worker_error = function(error) {
+    RemoteContext.prototype.remote_error = function(error) {
+        if (error.preventDefault) {
+            error.preventDefault();
+        }
+
+        // Defer interpretation of errors until the testing protocol has
+        // started and the remote test's `allow_uncaught_exception` property
+        // is available.
+        if (!this.started) {
+            this.early_exception = error;
+        } else if (!this.allow_uncaught_exception) {
+            this.report_uncaught(error);
+        }
+    };
+
+    RemoteContext.prototype.report_uncaught = function(error) {
         var message = error.message || String(error);
         var filename = (error.filename ? " " + error.filename: "");
-        // FIXME: Display worker error states separately from main document
-        // error state.
-        this.worker_done({
-            status: {
-                status: tests.status.ERROR,
-                message: "Error in worker" + filename + ": " + message
-            }
-        });
-        error.preventDefault();
+        // FIXME: Display remote error states separately from main document
+        // error state.
+        tests.set_status(tests.status.ERROR,
+                         "Error in remote" + filename + ": " + message,
+                         error.stack);
+    };
+
+    RemoteContext.prototype.start = function(data) {
+        this.started = true;
+        this.allow_uncaught_exception = data.properties.allow_uncaught_exception;
+
+        if (this.early_exception && !this.allow_uncaught_exception) {
+            this.report_uncaught(this.early_exception);
+        }
     };
 
-    RemoteWorker.prototype.test_state = function(data) {
+    RemoteContext.prototype.test_state = function(data) {
         var remote_test = this.tests[data.test.index];
         if (!remote_test) {
             remote_test = new RemoteTest(data.test);
@@ -1403,30 +2619,56 @@ policies and contribution forms [3].
         tests.notify_test_state(remote_test);
     };
 
-    RemoteWorker.prototype.test_done = function(data) {
+    RemoteContext.prototype.test_done = function(data) {
         var remote_test = this.tests[data.test.index];
         remote_test.update_state_from(data.test);
         remote_test.done();
         tests.result(remote_test);
     };
 
-    RemoteWorker.prototype.worker_done = function(data) {
+    RemoteContext.prototype.remote_done = function(data) {
         if (tests.status.status === null &&
             data.status.status !== data.status.OK) {
-            tests.status.status = data.status.status;
-            tests.status.message = data.status.message;
+            tests.set_status(data.status.status, data.status.message, data.status.stack);
+        }
+
+        for (let assert of data.asserts) {
+            var record = new AssertRecord();
+            record.assert_name = assert.assert_name;
+            record.args = assert.args;
+            record.test = assert.test != null ? this.tests[assert.test.index] : null;
+            record.status = assert.status;
+            record.stack = assert.stack;
+            tests.asserts_run.push(record);
         }
+
+        this.message_target.removeEventListener("message", this.message_handler);
         this.running = false;
-        this.worker = null;
+
+        // If remote context is cross origin assigning to onerror is not
+        // possible, so silently catch those errors.
+        try {
+          this.remote.onerror = null;
+        } catch (e) {
+          // Ignore.
+        }
+
+        this.remote = null;
+        this.message_target = null;
+        if (this.doneResolve) {
+            this.doneResolve();
+        }
+
         if (tests.all_done()) {
             tests.complete();
         }
     };
 
-    RemoteWorker.prototype.message_handlers = {
-        test_state: RemoteWorker.prototype.test_state,
-        result: RemoteWorker.prototype.test_done,
-        complete: RemoteWorker.prototype.worker_done
+    RemoteContext.prototype.message_handlers = {
+        start: RemoteContext.prototype.start,
+        test_state: RemoteContext.prototype.test_state,
+        result: RemoteContext.prototype.test_done,
+        complete: RemoteContext.prototype.remote_done
     };
 
     /*
@@ -1437,16 +2679,26 @@ policies and contribution forms [3].
     {
         this.status = null;
         this.message = null;
+        this.stack = null;
     }
 
     TestsStatus.statuses = {
         OK:0,
         ERROR:1,
-        TIMEOUT:2
+        TIMEOUT:2,
+        PRECONDITION_FAILED:3
     };
 
     TestsStatus.prototype = merge({}, TestsStatus.statuses);
 
+    TestsStatus.prototype.formats = {
+        0: "OK",
+        1: "Error",
+        2: "Timeout",
+        3: "Optional Feature Unsupported"
+    }
+
+
     TestsStatus.prototype.structured_clone = function()
     {
         if (!this._structured_clone) {
@@ -1454,12 +2706,34 @@ policies and contribution forms [3].
             msg = msg ? String(msg) : msg;
             this._structured_clone = merge({
                 status:this.status,
-                message:msg
+                message:msg,
+                stack:this.stack
             }, TestsStatus.statuses);
         }
         return this._structured_clone;
     };
 
+    TestsStatus.prototype.format_status = function() {
+        return this.formats[this.status];
+    }
+
+    function AssertRecord(test, assert_name, args = []) {
+        this.assert_name = assert_name;
+        this.test = test;
+        // Avoid keeping complex objects alive
+        this.args = args.map(x => format_value(x).replace(/\n/g, " "));
+        this.status = null;
+    }
+
+    AssertRecord.prototype.structured_clone = function() {
+        return {
+            assert_name: this.assert_name,
+            test: this.test ? this.test.structured_clone() : null,
+            args: this.args,
+            status: this.status,
+        }
+    }
+
     function Tests()
     {
         this.tests = [];
@@ -1482,6 +2756,10 @@ policies and contribution forms [3].
         this.allow_uncaught_exception = false;
 
         this.file_is_test = false;
+        // This value is lazily initialized in order to avoid introducing a
+        // dependency on ECMAScript 2015 Promises to all tests.
+        this.promise_tests = null;
+        this.promise_setup_called = false;
 
         this.timeout_multiplier = 1;
         this.timeout_length = test_environment.test_timeout();
@@ -1492,7 +2770,20 @@ policies and contribution forms [3].
         this.test_done_callbacks = [];
         this.all_done_callbacks = [];
 
-        this.pending_workers = [];
+        this.hide_test_state = false;
+        this.pending_remotes = [];
+
+        this.current_test = null;
+        this.asserts_run = [];
+
+        // Track whether output is enabled, and thus whether or not we should
+        // track asserts.
+        //
+        // On workers we don't get properties set from testharnessreport.js, so
+        // we don't know whether or not to track asserts. To avoid the
+        // resulting performance hit, we assume we are not meant to. This means
+        // that assert tracking does not function on workers.
+        this.output = settings.output && 'document' in global_scope;
 
         this.status = new TestsStatus();
 
@@ -1532,8 +2823,19 @@ policies and contribution forms [3].
                     {
                         clearTimeout(this.timeout_id);
                     }
+                } else if (p == "single_test" && value) {
+                    this.set_file_is_test();
                 } else if (p == "timeout_multiplier") {
                     this.timeout_multiplier = value;
+                    if (this.timeout_length) {
+                         this.timeout_length *= this.timeout_multiplier;
+                    }
+                } else if (p == "hide_test_state") {
+                    this.hide_test_state = value;
+                } else if (p == "output") {
+                    this.output = value;
+                } else if (p === "debug") {
+                    settings.debug = value;
                 }
             }
         }
@@ -1542,8 +2844,10 @@ policies and contribution forms [3].
             try {
                 func();
             } catch (e) {
-                this.status.status = this.status.ERROR;
+                this.status.status = e instanceof OptionalFeatureUnsupportedError ? this.status.PRECONDITION_FAILED : this.status.ERROR;
                 this.status.message = String(e);
+                this.status.stack = e.stack ? e.stack : null;
+                this.complete();
             }
         }
         this.set_timeout();
@@ -1556,23 +2860,58 @@ policies and contribution forms [3].
         this.wait_for_finish = true;
         this.file_is_test = true;
         // Create the test, which will add it to the list of tests
-        async_test();
+        tests.current_test = async_test();
+    };
+
+    Tests.prototype.set_status = function(status, message, stack)
+    {
+        this.status.status = status;
+        this.status.message = message;
+        this.status.stack = stack ? stack : null;
     };
 
     Tests.prototype.set_timeout = function() {
-        var this_obj = this;
-        clearTimeout(this.timeout_id);
-        if (this.timeout_length !== null) {
-            this.timeout_id = setTimeout(function() {
-                                             this_obj.timeout();
-                                         }, this.timeout_length);
+        if (global_scope.clearTimeout) {
+            var this_obj = this;
+            clearTimeout(this.timeout_id);
+            if (this.timeout_length !== null) {
+                this.timeout_id = setTimeout(function() {
+                                                 this_obj.timeout();
+                                             }, this.timeout_length);
+            }
         }
     };
 
     Tests.prototype.timeout = function() {
+        var test_in_cleanup = null;
+
         if (this.status.status === null) {
-            this.status.status = this.status.TIMEOUT;
+            forEach(this.tests,
+                    function(test) {
+                        // No more than one test is expected to be in the
+                        // "CLEANUP" phase at any time
+                        if (test.phase === test.phases.CLEANING) {
+                            test_in_cleanup = test;
+                        }
+
+                        test.phase = test.phases.COMPLETE;
+                    });
+
+            // Timeouts that occur while a test is in the "cleanup" phase
+            // indicate that some global state was not properly reverted. This
+            // invalidates the overall test execution, so the timeout should be
+            // reported as an error and cancel the execution of any remaining
+            // tests.
+            if (test_in_cleanup) {
+                this.status.status = this.status.ERROR;
+                this.status.message = "Timeout while running cleanup for " +
+                    "test named \"" + test_in_cleanup.name + "\".";
+                tests.status.stack = null;
+            } else {
+                this.status.status = this.status.TIMEOUT;
+            }
         }
+
         this.complete();
     };
 
@@ -1603,10 +2942,10 @@ policies and contribution forms [3].
     };
 
     Tests.prototype.all_done = function() {
-        return (this.tests.length > 0 && test_environment.all_loaded &&
-                this.num_pending === 0 && !this.wait_for_finish &&
+        return this.tests.length > 0 && test_environment.all_loaded &&
+                (this.num_pending === 0 || this.is_aborted) && !this.wait_for_finish &&
                 !this.processing_callbacks &&
-                !this.pending_workers.some(function(w) { return w.running; }));
+                !this.pending_remotes.some(function(w) { return w.running; });
     };
 
     Tests.prototype.start = function() {
@@ -1625,10 +2964,11 @@ policies and contribution forms [3].
 
     Tests.prototype.result = function(test)
     {
-        if (this.phase > this.phases.HAVE_RESULTS) {
-            return;
+        // If the harness has already transitioned beyond the `HAVE_RESULTS`
+        // phase, subsequent tests should not cause it to revert.
+        if (this.phase <= this.phases.HAVE_RESULTS) {
+            this.phase = this.phases.HAVE_RESULTS;
         }
-        this.phase = this.phases.HAVE_RESULTS;
         this.num_pending--;
         this.notify_result(test);
     };
@@ -1651,47 +2991,211 @@ policies and contribution forms [3].
         if (this.phase === this.phases.COMPLETE) {
             return;
         }
-        this.phase = this.phases.COMPLETE;
         var this_obj = this;
-        this.tests.forEach(
-            function(x)
-            {
-                if (x.phase < x.phases.COMPLETE) {
-                    this_obj.notify_result(x);
-                    x.cleanup();
-                    x.phase = x.phases.COMPLETE;
-                }
-            }
-        );
-        this.notify_complete();
+        var all_complete = function() {
+            this_obj.phase = this_obj.phases.COMPLETE;
+            this_obj.notify_complete();
+        };
+        var incomplete = filter(this.tests,
+                                function(test) {
+                                    return test.phase < test.phases.COMPLETE;
+                                });
+
+        /**
+         * To preserve legacy behavior, overall test completion must be
+         * signaled synchronously.
+         */
+        if (incomplete.length === 0) {
+            all_complete();
+            return;
+        }
+
+        all_async(incomplete,
+                  function(test, testDone)
+                  {
+                      if (test.phase === test.phases.INITIAL) {
+                          test.phase = test.phases.COMPLETE;
+                          testDone();
+                      } else {
+                          add_test_done_callback(test, testDone);
+                          test.cleanup();
+                      }
+                  },
+                  all_complete);
+    };
+
+    Tests.prototype.set_assert = function(assert_name, args) {
+        this.asserts_run.push(new AssertRecord(this.current_test, assert_name, args))
+    }
+
+    Tests.prototype.set_assert_status = function(status, stack) {
+        let assert_record = this.asserts_run[this.asserts_run.length - 1];
+        assert_record.status = status;
+        assert_record.stack = stack;
+    }
+
+    /**
+     * Update the harness status to reflect an unrecoverable harness error that
+     * should cancel all further testing. Update all previously-defined tests
+     * which have not yet started to indicate that they will not be executed.
+     */
+    Tests.prototype.abort = function() {
+        this.status.status = this.status.ERROR;
+        this.is_aborted = true;
+
+        forEach(this.tests,
+                function(test) {
+                    if (test.phase === test.phases.INITIAL) {
+                        test.phase = test.phases.COMPLETE;
+                    }
+                });
+    };
+
+    /*
+     * Determine if any tests share the same `name` property. Return an array
+     * containing the names of any such duplicates.
+     */
+    Tests.prototype.find_duplicates = function() {
+        var names = Object.create(null);
+        var duplicates = [];
+
+        forEach (this.tests,
+                 function(test)
+                 {
+                     if (test.name in names && duplicates.indexOf(test.name) === -1) {
+                        duplicates.push(test.name);
+                     }
+                     names[test.name] = true;
+                 });
+
+        return duplicates;
     };
 
+    function code_unit_str(char) {
+        return 'U+' + char.charCodeAt(0).toString(16);
+    }
+
+    function sanitize_unpaired_surrogates(str) {
+        return str.replace(
+            /([\ud800-\udbff]+)(?![\udc00-\udfff])|(^|[^\ud800-\udbff])([\udc00-\udfff]+)/g,
+            function(_, low, prefix, high) {
+                var output = prefix || "";  // prefix may be undefined
+                var string = low || high;  // only one of these alternates can match
+                for (var i = 0; i < string.length; i++) {
+                    output += code_unit_str(string[i]);
+                }
+                return output;
+            });
+    }
+
+    function sanitize_all_unpaired_surrogates(tests) {
+        forEach (tests,
+                 function (test)
+                 {
+                     var sanitized = sanitize_unpaired_surrogates(test.name);
+
+                     if (test.name !== sanitized) {
+                         test.name = sanitized;
+                         delete test._structured_clone;
+                     }
+                 });
+    }
+
     Tests.prototype.notify_complete = function() {
         var this_obj = this;
+        var duplicates;
+
         if (this.status.status === null) {
-            this.status.status = this.status.OK;
+            duplicates = this.find_duplicates();
+
+            // Some transports adhere to UTF-8's restriction on unpaired
+            // surrogates. Sanitize the titles so that the results can be
+            // consistently sent via all transports.
+            sanitize_all_unpaired_surrogates(this.tests);
+
+            // Test names are presumed to be unique within test files--this
+            // allows consumers to use them for identification purposes.
+            // Duplicated names violate this expectation and should therefore
+            // be reported as an error.
+            if (duplicates.length) {
+                this.status.status = this.status.ERROR;
+                this.status.message =
+                   duplicates.length + ' duplicate test name' +
+                   (duplicates.length > 1 ? 's' : '') + ': "' +
+                   duplicates.join('", "') + '"';
+            } else {
+                this.status.status = this.status.OK;
+            }
         }
 
         forEach (this.all_done_callbacks,
                  function(callback)
                  {
-                     callback(this_obj.tests, this_obj.status);
+                     callback(this_obj.tests, this_obj.status, this_obj.asserts_run);
                  });
     };
 
+    /*
+     * Constructs a RemoteContext that tracks tests from a specific worker.
+     */
+    Tests.prototype.create_remote_worker = function(worker) {
+        var message_port;
+
+        if (is_service_worker(worker)) {
+            message_port = navigator.serviceWorker;
+            worker.postMessage({type: "connect"});
+        } else if (is_shared_worker(worker)) {
+            message_port = worker.port;
+            message_port.start();
+        } else {
+            message_port = worker;
+        }
+
+        return new RemoteContext(worker, message_port);
+    };
+
+    /*
+     * Constructs a RemoteContext that tracks tests from a specific window.
+     */
+    Tests.prototype.create_remote_window = function(remote) {
+        remote.postMessage({type: "getmessages"}, "*");
+        return new RemoteContext(
+            remote,
+            window,
+            function(msg) {
+                return msg.source === remote;
+            }
+        );
+    };
+
     Tests.prototype.fetch_tests_from_worker = function(worker) {
         if (this.phase >= this.phases.COMPLETE) {
             return;
         }
 
-        this.pending_workers.push(new RemoteWorker(worker));
+        var remoteContext = this.create_remote_worker(worker);
+        this.pending_remotes.push(remoteContext);
+        return remoteContext.done;
     };
 
     function fetch_tests_from_worker(port) {
-        tests.fetch_tests_from_worker(port);
+        return tests.fetch_tests_from_worker(port);
     }
     expose(fetch_tests_from_worker, 'fetch_tests_from_worker');
 
+    Tests.prototype.fetch_tests_from_window = function(remote) {
+        if (this.phase >= this.phases.COMPLETE) {
+            return;
+        }
+
+        this.pending_remotes.push(this.create_remote_window(remote));
+    };
+
+    function fetch_tests_from_window(window) {
+        tests.fetch_tests_from_window(window);
+    }
+    expose(fetch_tests_from_window, 'fetch_tests_from_window');
+
     function timeout() {
         if (tests.timeout_length === null) {
             tests.timeout();
@@ -1707,14 +3211,12 @@ policies and contribution forms [3].
         tests.test_state_callbacks.push(callback);
     }
 
-    function add_result_callback(callback)
-    {
+    function add_result_callback(callback) {
         tests.test_done_callbacks.push(callback);
     }
 
-    function add_completion_callback(callback)
-    {
-       tests.all_done_callbacks.push(callback);
+    function add_completion_callback(callback) {
+        tests.all_done_callbacks.push(callback);
     }
 
     expose(add_start_callback, 'add_start_callback');
@@ -1722,6 +3224,29 @@ policies and contribution forms [3].
     expose(add_result_callback, 'add_result_callback');
     expose(add_completion_callback, 'add_completion_callback');
 
+    function remove(array, item) {
+        var index = array.indexOf(item);
+        if (index > -1) {
+            array.splice(index, 1);
+        }
+    }
+
+    function remove_start_callback(callback) {
+        remove(tests.start_callbacks, callback);
+    }
+
+    function remove_test_state_callback(callback) {
+        remove(tests.test_state_callbacks, callback);
+    }
+
+    function remove_result_callback(callback) {
+        remove(tests.test_done_callbacks, callback);
+    }
+
+    function remove_completion_callback(callback) {
+       remove(tests.all_done_callbacks, callback);
+    }
+
     /*
      * Output listener
     */
@@ -1763,6 +3288,9 @@ policies and contribution forms [3].
 
     Output.prototype.resolve_log = function() {
         var output_document;
+        if (this.output_node) {
+            return;
+        }
         if (typeof this.output_document === "function") {
             output_document = this.output_document.apply(undefined);
         } else {
@@ -1773,12 +3301,34 @@ policies and contribution forms [3].
         }
         var node = output_document.getElementById("log");
         if (!node) {
-            if (!document.body || document.readyState == "loading") {
+            if (output_document.readyState === "loading") {
                 return;
             }
-            node = output_document.createElement("div");
+            node = output_document.createElementNS("http://www.w3.org/1999/xhtml", "div");
             node.id = "log";
-            output_document.body.appendChild(node);
+            if (output_document.body) {
+                output_document.body.appendChild(node);
+            } else {
+                var root = output_document.documentElement;
+                var is_html = (root &&
+                               root.namespaceURI == "http://www.w3.org/1999/xhtml" &&
+                               root.localName == "html");
+                var is_svg = (output_document.defaultView &&
+                              "SVGSVGElement" in output_document.defaultView &&
+                              root instanceof output_document.defaultView.SVGSVGElement);
+                if (is_svg) {
+                    var foreignObject = output_document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
+                    foreignObject.setAttribute("width", "100%");
+                    foreignObject.setAttribute("height", "100%");
+                    root.appendChild(foreignObject);
+                    foreignObject.appendChild(node);
+                } else if (is_html) {
+                    root.appendChild(output_document.createElementNS("http://www.w3.org/1999/xhtml", "body"))
+                        .appendChild(node);
+                } else {
+                    root.appendChild(node);
+                }
+            }
         }
         this.output_document = output_document;
         this.output_node = node;
@@ -1788,15 +3338,15 @@ policies and contribution forms [3].
         if (this.phase < this.STARTED) {
             this.init();
         }
-        if (!this.enabled) {
+        if (!this.enabled || this.phase === this.COMPLETE) {
             return;
         }
+        this.resolve_log();
         if (this.phase < this.HAVE_RESULTS) {
-            this.resolve_log();
             this.phase = this.HAVE_RESULTS;
         }
         var done_count = tests.tests.length - tests.num_pending;
-        if (this.output_node) {
+        if (this.output_node && !tests.hide_test_state) {
             if (done_count < 100 ||
                 (done_count < 1000 && done_count % 100 === 0) ||
                 done_count % 1000 === 0) {
@@ -1807,7 +3357,7 @@ policies and contribution forms [3].
         }
     };
 
-    Output.prototype.show_results = function (tests, harness_status) {
+    Output.prototype.show_results = function (tests, harness_status, asserts_run) {
         if (this.phase >= this.COMPLETE) {
             return;
         }
@@ -1829,49 +3379,17 @@ policies and contribution forms [3].
             log.removeChild(log.lastChild);
         }
 
-        var script_prefix = null;
-        var scripts = document.getElementsByTagName("script");
-        for (var i = 0; i < scripts.length; i++) {
-            var src;
-            if (scripts[i].src) {
-                src = scripts[i].src;
-            } else if (scripts[i].href) {
-                //SVG case
-                src = scripts[i].href.baseVal;
-            }
-
-            var matches = src && src.match(/^(.*\/|)testharness\.js$/);
-            if (matches) {
-                script_prefix = matches[1];
-                break;
-            }
-        }
-
-        if (script_prefix !== null) {
-            var stylesheet = output_document.createElementNS(xhtml_ns, "link");
-            stylesheet.setAttribute("rel", "stylesheet");
-            stylesheet.setAttribute("href", script_prefix + "testharness.css");
-            var heads = output_document.getElementsByTagName("head");
-            if (heads.length) {
-                heads[0].appendChild(stylesheet);
-            }
+        var stylesheet = output_document.createElementNS(xhtml_ns, "style");
+        stylesheet.textContent = stylesheetContent;
+        var heads = output_document.getElementsByTagName("head");
+        if (heads.length) {
+            heads[0].appendChild(stylesheet);
         }
 
-        var status_text_harness = {};
-        status_text_harness[harness_status.OK] = "OK";
-        status_text_harness[harness_status.ERROR] = "Error";
-        status_text_harness[harness_status.TIMEOUT] = "Timeout";
-
-        var status_text = {};
-        status_text[Test.prototype.PASS] = "Pass";
-        status_text[Test.prototype.FAIL] = "Fail";
-        status_text[Test.prototype.TIMEOUT] = "Timeout";
-        status_text[Test.prototype.NOTRUN] = "Not Run";
-
         var status_number = {};
         forEach(tests,
                 function(test) {
-                    var status = status_text[test.status];
+                    var status = test.format_status();
                     if (status_number.hasOwnProperty(status)) {
                         status_number[status] += 1;
                     } else {
@@ -1888,8 +3406,7 @@ policies and contribution forms [3].
                                 ["h2", {}, "Summary"],
                                 function()
                                 {
-
-                                    var status = status_text_harness[harness_status.status];
+                                    var status = harness_status.format_status();
                                     var rv = [["section", {},
                                                ["p", {},
                                                 "Harness status: ",
@@ -1901,6 +3418,9 @@ policies and contribution forms [3].
 
                                     if (harness_status.status === harness_status.ERROR) {
                                         rv[0].push(["pre", {}, harness_status.message]);
+                                        if (harness_status.stack) {
+                                            rv[0].push(["pre", {}, harness_status.stack]);
+                                        }
                                     }
                                     return rv;
                                 },
@@ -1908,13 +3428,14 @@ policies and contribution forms [3].
                                 function() {
                                     var rv = [["div", {}]];
                                     var i = 0;
-                                    while (status_text.hasOwnProperty(i)) {
-                                        if (status_number.hasOwnProperty(status_text[i])) {
-                                            var status = status_text[i];
-                                            rv[0].push(["div", {"class":status_class(status)},
+                                    while (Test.prototype.status_formats.hasOwnProperty(i)) {
+                                        if (status_number.hasOwnProperty(Test.prototype.status_formats[i])) {
+                                            var status = Test.prototype.status_formats[i];
+                                            rv[0].push(["div", {},
                                                         ["label", {},
                                                          ["input", {type:"checkbox", checked:"checked"}],
-                                                         status_number[status] + " " + status]]);
+                                                         status_number[status] + " ",
+                                                         ["span", {"class":status_class(status)}, status]]]);
                                         }
                                         i++;
                                     }
@@ -1934,13 +3455,13 @@ policies and contribution forms [3].
                                      e.preventDefault();
                                      return;
                                  }
-                                 var result_class = element.parentNode.getAttribute("class");
+                                 var result_class = element.querySelector("span[class]").getAttribute("class");
                                  var style_element = output_document.querySelector("style#hide-" + result_class);
                                  var input_element = element.querySelector("input");
                                  if (!style_element && !input_element.checked) {
                                      style_element = output_document.createElementNS(xhtml_ns, "style");
                                      style_element.id = "hide-" + result_class;
-                                     style_element.textContent = "table#results > tbody > tr."+result_class+"{display:none}";
+                                     style_element.textContent = "table#results > tbody > tr.overall-"+result_class+"{display:none}";
                                      output_document.body.appendChild(style_element);
                                  } else if (style_element && input_element.checked) {
                                      style_element.parentNode.removeChild(style_element);
@@ -1981,6 +3502,52 @@ policies and contribution forms [3].
             return '';
         }
 
+        var asserts_run_by_test = new Map();
+        asserts_run.forEach(assert => {
+            if (!asserts_run_by_test.has(assert.test)) {
+                asserts_run_by_test.set(assert.test, []);
+            }
+            asserts_run_by_test.get(assert.test).push(assert);
+        });
+
+        function get_asserts_output(test) {
+            var asserts = asserts_run_by_test.get(test);
+            if (!asserts) {
+                return "No asserts ran";
+            }
+            rv = "<table>";
+            rv += asserts.map(assert => {
+                var output_fn = "<strong>" + escape_html(assert.assert_name) + "</strong>(";
+                var prefix_len = output_fn.length;
+                var output_args = assert.args;
+                var output_len = output_args.reduce((prev, current) => prev+current, prefix_len);
+                if (output_len[output_len.length - 1] > 50) {
+                    output_args = output_args.map((x, i) =>
+                    (i > 0 ? "  ".repeat(prefix_len) : "" )+ x + (i < output_args.length - 1 ? ",\n" : ""));
+                } else {
+                    output_args = output_args.map((x, i) => x + (i < output_args.length - 1 ? ", " : ""));
+                }
+                output_fn += escape_html(output_args.join(""));
+                output_fn += ')';
+                var output_location;
+                if (assert.stack) {
+                    output_location = assert.stack.split("\n", 1)[0].replace(/@?\w+:\/\/[^ "\/]+(?::\d+)?/g, " ");
+                }
+                return "<tr class='overall-" +
+                    status_class(Test.prototype.status_formats[assert.status]) + "'>" +
+                    "<td class='" +
+                    status_class(Test.prototype.status_formats[assert.status]) + "'>" +
+                    Test.prototype.status_formats[assert.status] + "</td>" +
+                    "<td><pre>" +
+                    output_fn +
+                    (output_location ? "\n" + escape_html(output_location) : "") +
+                    "</pre></td></tr>";
+            }
+            ).join("\n");
+            rv += "</table>";
+            return rv;
+        }
+
         log.appendChild(document.createElementNS(xhtml_ns, "section"));
         var assertions = has_assertions();
         var html = "<h2>Details</h2><table id='results' " + (assertions ? "class='assertions'" : "" ) + ">" +
@@ -1989,16 +3556,26 @@ policies and contribution forms [3].
             "<th>Message</th></tr></thead>" +
             "<tbody>";
         for (var i = 0; i < tests.length; i++) {
-            html += '<tr class="' +
-                escape_html(status_class(status_text[tests[i].status])) +
-                '"><td>' +
-                escape_html(status_text[tests[i].status]) +
+            var test = tests[i];
+            html += '<tr class="overall-' +
+                status_class(test.format_status()) +
+                '">' +
+                '<td class="' +
+                status_class(test.format_status()) +
+                '">' +
+                test.format_status() +
                 "</td><td>" +
-                escape_html(tests[i].name) +
+                escape_html(test.name) +
                 "</td><td>" +
-                (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "") +
-                escape_html(tests[i].message ? tests[i].message : " ") +
-                "</td></tr>";
+                (assertions ? escape_html(get_assertion(test)) + "</td><td>" : "") +
+                escape_html(test.message ? tests[i].message : " ") +
+                (tests[i].stack ? "<pre>" +
+                 escape_html(tests[i].stack) +
+                 "</pre>": "");
+            if (!(test instanceof RemoteTest)) {
+                 html += "<details><summary>Asserts run</summary>" + get_asserts_output(test) + "</details>"
+            }
+            html += "</td></tr>";
         }
         html += "</tbody></table>";
         try {
@@ -2014,7 +3591,7 @@ policies and contribution forms [3].
     /*
      * Template code
      *
-     * A template is just a javascript structure. An element is represented as:
+     * A template is just a JavaScript structure. An element is represented as:
      *
      * [tag_name, {attr_name:attr_value}, child1, child2]
      *
@@ -2176,13 +3753,10 @@ policies and contribution forms [3].
     }
 
     /*
-     * Utility funcions
+     * Utility functions
      */
     function assert(expected_true, function_name, description, error, substitutions)
     {
-        if (tests.tests.length === 0) {
-            tests.set_file_is_test();
-        }
         if (expected_true !== true) {
             var msg = make_message(function_name, description,
                                    error, substitutions);
@@ -2192,12 +3766,68 @@ policies and contribution forms [3].
 
     function AssertionError(message)
     {
+        if (typeof message == "string") {
+            message = sanitize_unpaired_surrogates(message);
+        }
         this.message = message;
+        this.stack = get_stack();
     }
+    expose(AssertionError, "AssertionError");
 
-    AssertionError.prototype.toString = function() {
-        return this.message;
-    };
+    AssertionError.prototype = Object.create(Error.prototype);
+
+    const get_stack = function() {
+        var stack = new Error().stack;
+        // IE11 does not initialize 'Error.stack' until the object is thrown.
+        if (!stack) {
+            try {
+                throw new Error();
+            } catch (e) {
+                stack = e.stack;
+            }
+        }
+
+        // 'Error.stack' is not supported in all browsers/versions
+        if (!stack) {
+            return "(Stack trace unavailable)";
+        }
+
+        var lines = stack.split("\n");
+
+        // Create a pattern to match stack frames originating within testharness.js.  These include the
+        // script URL, followed by the line/col (e.g., '/resources/testharness.js:120:21').
+        // Escape the URL per http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
+        // in case it contains RegExp characters.
+        var script_url = get_script_url();
+        var re_text = script_url ? script_url.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') : "\\btestharness.js";
+        var re = new RegExp(re_text + ":\\d+:\\d+");
+
+        // Some browsers include a preamble that specifies the type of the error object.  Skip this by
+        // advancing until we find the first stack frame originating from testharness.js.
+        var i = 0;
+        while (!re.test(lines[i]) && i < lines.length) {
+            i++;
+        }
+
+        // Then skip the top frames originating from testharness.js to begin the stack at the test code.
+        while (re.test(lines[i]) && i < lines.length) {
+            i++;
+        }
+
+        // Paranoid check that we didn't skip all frames.  If so, return the original stack unmodified.
+        if (i >= lines.length) {
+            return stack;
+        }
+
+        return lines.slice(i).join("\n");
+    }
+
+    function OptionalFeatureUnsupportedError(message)
+    {
+        AssertionError.call(this, message);
+    }
+    OptionalFeatureUnsupportedError.prototype = Object.create(AssertionError.prototype);
+    expose(OptionalFeatureUnsupportedError, "OptionalFeatureUnsupportedError");
 
     function make_message(function_name, description, error, substitutions)
     {
@@ -2243,7 +3873,7 @@ policies and contribution forms [3].
         Array.prototype.push.apply(array, items);
     }
 
-    function forEach (array, callback, thisObj)
+    function forEach(array, callback, thisObj)
     {
         for (var i = 0; i < array.length; i++) {
             if (array.hasOwnProperty(i)) {
@@ -2252,6 +3882,57 @@ policies and contribution forms [3].
         }
     }
 
+    /**
+     * Immediately invoke a "iteratee" function with a series of values in
+     * parallel and invoke a final "done" function when all of the "iteratee"
+     * invocations have signaled completion.
+     *
+     * If all callbacks complete synchronously (or if no callbacks are
+     * specified), the `done_callback` will be invoked synchronously. It is the
+     * responsibility of the caller to ensure asynchronicity in cases where
+     * that is desired.
+     *
+     * @param {array} value Zero or more values to use in the invocation of
+     *                      `iter_callback`
+     * @param {function} iter_callback A function that will be invoked once for
+     *                                 each of the provided `values`. Two
+     *                                 arguments will be available in each
+     *                                 invocation: the value from `values` and
+     *                                 a function that must be invoked to
+     *                                 signal completion
+     * @param {function} done_callback A function that will be invoked after
+     *                                 all operations initiated by the
+     *                                 `iter_callback` function have signaled
+     *                                 completion
+     */
+    function all_async(values, iter_callback, done_callback)
+    {
+        var remaining = values.length;
+
+        if (remaining === 0) {
+            done_callback();
+        }
+
+        forEach(values,
+                function(element) {
+                    var invoked = false;
+                    var elDone = function() {
+                        if (invoked) {
+                            return;
+                        }
+
+                        invoked = true;
+                        remaining -= 1;
+
+                        if (remaining === 0) {
+                            done_callback();
+                        }
+                    };
+
+                    iter_callback(element, elDone);
+                });
+    }
+
     function merge(a,b)
     {
         var rv = {};
@@ -2268,7 +3949,7 @@ policies and contribution forms [3].
     function expose(object, name)
     {
         var components = name.split(".");
-        var target = test_environment.global_scope();
+        var target = global_scope;
         for (var i = 0; i < components.length - 1; i++) {
             if (!(components[i] in target)) {
                 target[components[i]] = {};
@@ -2287,18 +3968,62 @@ policies and contribution forms [3].
         }
     }
 
+    /** Returns the 'src' URL of the first <script> tag in the page to include the file 'testharness.js'. */
+    function get_script_url()
+    {
+        if (!('document' in global_scope)) {
+            return undefined;
+        }
+
+        var scripts = document.getElementsByTagName("script");
+        for (var i = 0; i < scripts.length; i++) {
+            var src;
+            if (scripts[i].src) {
+                src = scripts[i].src;
+            } else if (scripts[i].href) {
+                //SVG case
+                src = scripts[i].href.baseVal;
+            }
+
+            var matches = src && src.match(/^(.*\/|)testharness\.js$/);
+            if (matches) {
+                return src;
+            }
+        }
+        return undefined;
+    }
+
+    /** Returns the <title> or filename or "Untitled" */
+    function get_title()
+    {
+        if ('document' in global_scope) {
+            //Don't use document.title to work around an Opera/Presto bug in XHTML documents
+            var title = document.getElementsByTagName("title")[0];
+            if (title && title.firstChild && title.firstChild.data) {
+                return title.firstChild.data;
+            }
+        }
+        if ('META_TITLE' in global_scope && META_TITLE) {
+            return META_TITLE;
+        }
+        if ('location' in global_scope) {
+            return location.pathname.substring(location.pathname.lastIndexOf('/') + 1, location.pathname.indexOf('.'));
+        }
+        return "Untitled";
+    }
+
     function supports_post_message(w)
     {
         var supports;
         var type;
-        // Given IE  implements postMessage across nested iframes but not across
+        // Given IE implements postMessage across nested iframes but not across
         // windows or tabs, you can't infer cross-origin communication from the presence
         // of postMessage on the current window object only.
         //
         // Touching the postMessage prop on a window can throw if the window is
         // not from the same origin AND post message is not supported in that
         // browser. So just doing an existence test here won't do, you also need
-        // to wrap it in a try..cacth block.
+        // to wrap it in a try..catch block.
         try {
             type = typeof w.postMessage;
             if (type === "function") {
@@ -2330,24 +4055,164 @@ policies and contribution forms [3].
 
     var tests = new Tests();
 
-    addEventListener("error", function(e) {
-        if (tests.file_is_test) {
-            var test = tests.tests[0];
-            if (test.phase >= test.phases.HAS_RESULT) {
-                return;
+    if (global_scope.addEventListener) {
+        var error_handler = function(error, message, stack) {
+            var optional_unsupported = error instanceof OptionalFeatureUnsupportedError;
+            if (tests.file_is_test) {
+                var test = tests.tests[0];
+                if (test.phase >= test.phases.HAS_RESULT) {
+                    return;
+                }
+                var status = optional_unsupported ? test.PRECONDITION_FAILED : test.FAIL;
+                test.set_status(status, message, stack);
+                test.phase = test.phases.HAS_RESULT;
+            } else if (!tests.allow_uncaught_exception) {
+                var status = optional_unsupported ? tests.status.PRECONDITION_FAILED : tests.status.ERROR;
+                tests.status.status = status;
+                tests.status.message = message;
+                tests.status.stack = stack;
             }
+
+            // Do not transition to the "complete" phase if the test has been
+            // configured to allow uncaught exceptions. This gives the test an
+            // opportunity to define subtests based on the exception reporting
+            // behavior.
+            if (!tests.allow_uncaught_exception) {
+                done();
+            }
+        };
+
+        addEventListener("error", function(e) {
             var message = e.message;
-            test.set_status(test.FAIL, message);
-            test.phase = test.phases.HAS_RESULT;
-            test.done();
-            done();
-        } else if (!tests.allow_uncaught_exception) {
-            tests.status.status = tests.status.ERROR;
-            tests.status.message = e.message;
-        }
-    });
+            var stack;
+            if (e.error && e.error.stack) {
+                stack = e.error.stack;
+            } else {
+                stack = e.filename + ":" + e.lineno + ":" + e.colno;
+            }
+            error_handler(e.error, message, stack);
+        }, false);
+
+        addEventListener("unhandledrejection", function(e) {
+            var message;
+            if (e.reason && e.reason.message) {
+                message = "Unhandled rejection: " + e.reason.message;
+            } else {
+                message = "Unhandled rejection";
+            }
+            var stack;
+            if (e.reason && e.reason.stack) {
+                stack = e.reason.stack;
+            }
+            error_handler(e.reason, message, stack);
+        }, false);
+    }
 
     test_environment.on_tests_ready();
 
-})();
+    /**
+     * Stylesheet
+     */
+     var stylesheetContent = "\
+html {\
+    font-family:DejaVu Sans, Bitstream Vera Sans, Arial, Sans;\
+}\
+\
+#log .warning,\
+#log .warning a {\
+  color: black;\
+  background: yellow;\
+}\
+\
+#log .error,\
+#log .error a {\
+  color: white;\
+  background: red;\
+}\
+\
+section#summary {\
+    margin-bottom:1em;\
+}\
+\
+table#results {\
+    border-collapse:collapse;\
+    table-layout:fixed;\
+    width:100%;\
+}\
+\
+table#results > thead > tr > th:first-child,\
+table#results > tbody > tr > td:first-child {\
+    width:8em;\
+}\
+\
+table#results > thead > tr > th:last-child,\
+table#results > thead > tr > td:last-child {\
+    width:50%;\
+}\
+\
+table#results.assertions > thead > tr > th:last-child,\
+table#results.assertions > tbody > tr > td:last-child {\
+    width:35%;\
+}\
+\
+table#results > thead > > tr > th {\
+    padding:0;\
+    padding-bottom:0.5em;\
+    border-bottom:medium solid black;\
+}\
+\
+table#results > tbody > tr> td {\
+    padding:1em;\
+    padding-bottom:0.5em;\
+    border-bottom:thin solid black;\
+}\
+\
+.pass {\
+    color:green;\
+}\
+\
+.fail {\
+    color:red;\
+}\
+\
+tr.timeout {\
+    color:red;\
+}\
+\
+tr.notrun {\
+    color:blue;\
+}\
+\
+tr.optionalunsupported {\
+    color:blue;\
+}\
+\
+.ok {\
+    color:green;\
+}\
+\
+.error {\
+    color:red;\
+}\
+\
+.pass, .fail, .timeout, .notrun, .optionalunsupported .ok, .timeout, .error {\
+    font-variant:small-caps;\
+}\
+\
+table#results span {\
+    display:block;\
+}\
+\
+table#results span.expected {\
+    font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;\
+    white-space:pre;\
+}\
+\
+table#results span.actual {\
+    font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;\
+    white-space:pre;\
+}\
+";
+
+})(self);
 // vim: set expandtab shiftwidth=4 tabstop=4:
index d7df7e2fb532b5317ac7e09c260cca246d28906e..e5cb40fe0ef652be407d4c48b1c59391864cec7b 100755 (executable)
@@ -1,18 +1,9 @@
-/*global add_completion_callback, setup */
+/* global add_completion_callback */
+/* global setup */
+
 /*
- * This file is intended for vendors to implement
- * code needed to integrate testharness.js tests with their own test systems.
- *
- * The default implementation extracts metadata from the tests and validates
- * it against the cached version that should be present in the test source
- * file. If the cache is not found or is out of sync, source code suitable for
- * caching the metadata is optionally generated.
- *
- * The cached metadata is present for extraction by test processing tools that
- * are unable to execute javascript.
- *
- * Metadata is attached to tests via the properties parameter in the test
- * constructor. See testharness.js for details.
+ * This file is intended for vendors to implement code needed to integrate
+ * testharness.js tests with their own test systems.
  *
  * Typically test system integration will attach callbacks when each test has
  * run, using add_result_callback(callback(test)), or when the whole test file
  * parameters they are called with see testharness.js
  */
 
+function dump_test_results(tests, status) {
+    var results_element = document.createElement("script");
+    results_element.type = "text/json";
+    results_element.id = "__testharness__results__";
+    var test_results = tests.map(function(x) {
+        return {name:x.name, status:x.status, message:x.message, stack:x.stack}
+    });
+    var data = {test:window.location.href,
+                tests:test_results,
+                status: status.status,
+                message: status.message,
+                stack: status.stack};
+    results_element.textContent = JSON.stringify(data);
+
+    // To avoid a HierarchyRequestError with XML documents, ensure that 'results_element'
+    // is inserted at a location that results in a valid document.
+    var parent = document.body
+        ? document.body                 // <body> is required in XHTML documents
+        : document.documentElement;     // fallback for optional <body> in HTML5, SVG, etc.
+
+    parent.appendChild(results_element);
+}
 
-
-var metadata_generator = {
-
-    currentMetadata: {},
-    cachedMetadata: false,
-    metadataProperties: ['help', 'assert', 'author'],
-
-    error: function(message) {
-        var messageElement = document.createElement('p');
-        messageElement.setAttribute('class', 'error');
-        this.appendText(messageElement, message);
-
-        var summary = document.getElementById('summary');
-        if (summary) {
-            summary.parentNode.insertBefore(messageElement, summary);
-        }
-        else {
-            document.body.appendChild(messageElement);
-        }
-    },
-
-    /**
-     * Ensure property value has contact information
-     */
-    validateContact: function(test, propertyName) {
-        var result = true;
-        var value = test.properties[propertyName];
-        var values = Array.isArray(value) ? value : [value];
-        for (var index = 0; index < values.length; index++) {
-            value = values[index];
-            var re = /(\S+)(\s*)<(.*)>(.*)/;
-            if (! re.test(value)) {
-                re = /(\S+)(\s+)(http[s]?:\/\/)(.*)/;
-                if (! re.test(value)) {
-                    this.error('Metadata property "' + propertyName +
-                        '" for test: "' + test.name +
-                        '" must have name and contact information ' +
-                        '("name <email>" or "name http(s)://")');
-                    result = false;
-                }
-            }
-        }
-        return result;
-    },
-
-    /**
-     * Extract metadata from test object
-     */
-    extractFromTest: function(test) {
-        var testMetadata = {};
-        // filter out metadata from other properties in test
-        for (var metaIndex = 0; metaIndex < this.metadataProperties.length;
-             metaIndex++) {
-            var meta = this.metadataProperties[metaIndex];
-            if (test.properties.hasOwnProperty(meta)) {
-                if ('author' == meta) {
-                    this.validateContact(test, meta);
-                }
-                testMetadata[meta] = test.properties[meta];
-            }
-        }
-        return testMetadata;
-    },
-
-    /**
-     * Compare cached metadata to extracted metadata
-     */
-    validateCache: function() {
-        for (var testName in this.currentMetadata) {
-            if (! this.cachedMetadata.hasOwnProperty(testName)) {
-                return false;
-            }
-            var testMetadata = this.currentMetadata[testName];
-            var cachedTestMetadata = this.cachedMetadata[testName];
-            delete this.cachedMetadata[testName];
-
-            for (var metaIndex = 0; metaIndex < this.metadataProperties.length;
-                 metaIndex++) {
-                var meta = this.metadataProperties[metaIndex];
-                if (cachedTestMetadata.hasOwnProperty(meta) &&
-                    testMetadata.hasOwnProperty(meta)) {
-                    if (Array.isArray(cachedTestMetadata[meta])) {
-                      if (! Array.isArray(testMetadata[meta])) {
-                          return false;
-                      }
-                      if (cachedTestMetadata[meta].length ==
-                          testMetadata[meta].length) {
-                          for (var index = 0;
-                               index < cachedTestMetadata[meta].length;
-                               index++) {
-                              if (cachedTestMetadata[meta][index] !=
-                                  testMetadata[meta][index]) {
-                                  return false;
-                              }
-                          }
-                      }
-                      else {
-                          return false;
-                      }
-                    }
-                    else {
-                      if (Array.isArray(testMetadata[meta])) {
-                        return false;
-                      }
-                      if (cachedTestMetadata[meta] != testMetadata[meta]) {
-                        return false;
-                      }
-                    }
-                }
-                else if (cachedTestMetadata.hasOwnProperty(meta) ||
-                         testMetadata.hasOwnProperty(meta)) {
-                    return false;
-                }
-            }
-        }
-        for (var testName in this.cachedMetadata) {
-            return false;
-        }
-        return true;
-    },
-
-    appendText: function(elemement, text) {
-        elemement.appendChild(document.createTextNode(text));
-    },
-
-    jsonifyArray: function(arrayValue, indent) {
-        var output = '[';
-
-        if (1 == arrayValue.length) {
-            output += JSON.stringify(arrayValue[0]);
-        }
-        else {
-            for (var index = 0; index < arrayValue.length; index++) {
-                if (0 < index) {
-                    output += ',\n  ' + indent;
-                }
-                output += JSON.stringify(arrayValue[index]);
-            }
-        }
-        output += ']';
-        return output;
-    },
-
-    jsonifyObject: function(objectValue, indent) {
-        var output = '{';
-        var value;
-
-        var count = 0;
-        for (var property in objectValue) {
-            ++count;
-            if (Array.isArray(objectValue[property]) ||
-                ('object' == typeof(value))) {
-                ++count;
-            }
-        }
-        if (1 == count) {
-            for (var property in objectValue) {
-                output += ' "' + property + '": ' +
-                    JSON.stringify(objectValue[property]) +
-                    ' ';
-            }
-        }
-        else {
-            var first = true;
-            for (var property in objectValue) {
-                if (! first) {
-                    output += ',';
-                }
-                first = false;
-                output += '\n  ' + indent + '"' + property + '": ';
-                value = objectValue[property];
-                if (Array.isArray(value)) {
-                    output += this.jsonifyArray(value, indent +
-                        '                '.substr(0, 5 + property.length));
-                }
-                else if ('object' == typeof(value)) {
-                    output += this.jsonifyObject(value, indent + '  ');
-                }
-                else {
-                    output += JSON.stringify(value);
-                }
-            }
-            if (1 < output.length) {
-                output += '\n' + indent;
-            }
-        }
-        output += '}';
-        return output;
-    },
-
-    /**
-     * Generate javascript source code for captured metadata
-     * Metadata is in pretty-printed JSON format
-     */
-    generateSource: function() {
-        var source =
-            '<script id="metadata_cache">/*\n' +
-            this.jsonifyObject(this.currentMetadata, '') + '\n' +
-            '*/</script>\n';
-        return source;
-    },
-
-    /**
-     * Add element containing metadata source code
-     */
-    addSourceElement: function(event) {
-        var sourceWrapper = document.createElement('div');
-        sourceWrapper.setAttribute('id', 'metadata_source');
-
-        var instructions = document.createElement('p');
-        if (this.cachedMetadata) {
-            this.appendText(instructions,
-                'Replace the existing <script id="metadata_cache"> element ' +
-                'in the test\'s <head> with the following:');
-        }
-        else {
-            this.appendText(instructions,
-                'Copy the following into the <head> element of the test ' +
-                'or the test\'s metadata sidecar file:');
-        }
-        sourceWrapper.appendChild(instructions);
-
-        var sourceElement = document.createElement('pre');
-        this.appendText(sourceElement, this.generateSource());
-
-        sourceWrapper.appendChild(sourceElement);
-
-        var messageElement = document.getElementById('metadata_issue');
-        messageElement.parentNode.insertBefore(sourceWrapper,
-                                               messageElement.nextSibling);
-        messageElement.parentNode.removeChild(messageElement);
-
-        (event.preventDefault) ? event.preventDefault() :
-                                 event.returnValue = false;
-    },
-
-    /**
-     * Extract the metadata cache from the cache element if present
-     */
-    getCachedMetadata: function() {
-        var cacheElement = document.getElementById('metadata_cache');
-
-        if (cacheElement) {
-            var cacheText = cacheElement.firstChild.nodeValue;
-            var openBrace = cacheText.indexOf('{');
-            var closeBrace = cacheText.lastIndexOf('}');
-            if ((-1 < openBrace) && (-1 < closeBrace)) {
-                cacheText = cacheText.slice(openBrace, closeBrace + 1);
-                try {
-                    this.cachedMetadata = JSON.parse(cacheText);
-                }
-                catch (exc) {
-                    this.cachedMetadata = 'Invalid JSON in Cached metadata. ';
-                }
-            }
-            else {
-                this.cachedMetadata = 'Metadata not found in cache element. ';
-            }
-        }
-    },
-
-    /**
-     * Main entry point, extract metadata from tests, compare to cached version
-     * if present.
-     * If cache not present or differs from extrated metadata, generate an error
-     */
-    process: function(tests) {
-        for (var index = 0; index < tests.length; index++) {
-            var test = tests[index];
-            if (this.currentMetadata.hasOwnProperty(test.name)) {
-                this.error('Duplicate test name: ' + test.name);
-            }
-            else {
-                this.currentMetadata[test.name] = this.extractFromTest(test);
-            }
-        }
-
-        this.getCachedMetadata();
-
-        var message = null;
-        var messageClass = 'warning';
-        var showSource = false;
-
-        if (0 === tests.length) {
-            if (this.cachedMetadata) {
-                message = 'Cached metadata present but no tests. ';
-            }
-        }
-        else if (1 === tests.length) {
-            if (this.cachedMetadata) {
-                message = 'Single test files should not have cached metadata. ';
-            }
-            else {
-                var testMetadata = this.currentMetadata[tests[0].name];
-                for (var meta in testMetadata) {
-                    if (testMetadata.hasOwnProperty(meta)) {
-                        message = 'Single tests should not have metadata. ' +
-                                  'Move metadata to <head>. ';
-                        break;
-                    }
-                }
-            }
-        }
-        else {
-            if (this.cachedMetadata) {
-                messageClass = 'error';
-                if ('string' == typeof(this.cachedMetadata)) {
-                    message = this.cachedMetadata;
-                    showSource = true;
-                }
-                else if (! this.validateCache()) {
-                    message = 'Cached metadata out of sync. ';
-                    showSource = true;
-                }
-            }
-        }
-
-        if (message) {
-            var messageElement = document.createElement('p');
-            messageElement.setAttribute('id', 'metadata_issue');
-            messageElement.setAttribute('class', messageClass);
-            this.appendText(messageElement, message);
-
-            if (showSource) {
-                var link = document.createElement('a');
-                this.appendText(link, 'Click for source code.');
-                link.setAttribute('href', '#');
-                link.setAttribute('onclick',
-                                  'metadata_generator.addSourceElement(event)');
-                messageElement.appendChild(link);
-            }
-
-            var summary = document.getElementById('summary');
-            if (summary) {
-                summary.parentNode.insertBefore(messageElement, summary);
-            }
-            else {
-                var log = document.getElementById('log');
-                if (log) {
-                    log.appendChild(messageElement);
-                }
-            }
-        }
-    },
-
-    setup: function() {
-        add_completion_callback(
-            function (tests, harness_status) {
-                metadata_generator.process(tests, harness_status);
-            });
-    }
-};
-
-metadata_generator.setup();
+add_completion_callback(dump_test_results);
 
 /* If the parent window has a testharness_properties object,
  * we use this to provide the test settings. This is used by the
index 4b25719208b5af545ffea4306975c1e91cbda8a3..992a0cc6c80f01e32268bdfe522db5f0c5a030f9 100755 (executable)
         </specs>
       </testcase>
     </set>
+    <set name="MediaCapture_streams_IOT" type="js">
+      <capabilities>
+        <capability name="http://tizen.org/feature/profile"><value>COMMON</value></capability>
+      </capabilities>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-api.https" purpose="mediaDevices.getUserMedia() is present on navigator" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-api.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-empty-option-param.https" purpose="Tests that getUserMedia is rejected with a TypeError when used with an empty options parameter" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-empty-option-param.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-impossible-constraint.https" purpose="Tests that setting an impossible constraint in getUserMedia fails" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-impossible-constraint.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-invalid-facing-mode.https" purpose="Tests that setting an invalid facingMode constraint in getUserMedia fails" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-invalid-facing-mode.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_1" purpose="Test that setting video-only valid constraints inside of audio is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_2" purpose="Test that setting video-only invalid constraints inside of audio is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_3" purpose="Test that setting audio-only valid constraints inside of video is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_4" purpose="Test that setting audio-only invalid constraints inside of video is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-optional-constraint.https" purpose="Tests that setting an optional constraint in getUserMedia is handled as optional" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-optional-constraint.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-required-constraint-with-ideal-value" purpose="Tests that setting a required constraint with an ideal value in getUserMedia works" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-required-constraint-with-ideal-value.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-trivial-constraint.https" purpose="Tests that setting a trivial mandatory constraint in getUserMedia works" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-trivial-constraint.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-unknownkey-option-param.https" purpose="Tests that getUserMedia is rejected with a TypeError when used with an unknown constraint" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-unknownkey-option-param.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-enumerateDevices-returned-objects.https" purpose="enumerateDevices returns expected objects in case device-info permission is granted" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices-returned-objects.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-enumerateDevices.https" purpose="InputDeviceInfo is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_1" purpose="navigator.mediaDevices.getSupportedConstraints exists" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_2" purpose="width is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_3" purpose="height is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_4" purpose="aspectRatio is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_5" purpose="frameRate is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_6" purpose="facingMode is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_7" purpose="resizeMode is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_8" purpose="sampleRate is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_9" purpose="sampleSize is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_10" purpose="echoCancellation is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=10</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_11" purpose="autoGainControl is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=11</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_12" purpose="noiseSuppression is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=12</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_13" purpose="latency is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=13</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_14" purpose="channelCount is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=14</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_15" purpose="deviceId is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=15</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_16" purpose="groupId is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=16</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_1" purpose="mediaDevices.getUserMedia() is present on navigator" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_2" purpose="groupId is correctly supported by getUserMedia() for video devices" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_3" purpose="groupId is correctly supported by getUserMedia() for audio devices" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_4" purpose="getUserMedia() supports setting none as resizeMode." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_5" purpose="getUserMedia() supports setting crop-and-scale as resizeMode." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_6" purpose="getUserMedia() fails with exact invalid resizeMode." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-firstframe.https_1" purpose="Tests that loading a MediaStream in a media element eventually results in canplay even when not playing or autoplaying" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-firstframe.https.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-firstframe.https_2" purpose="Tests that loading a MediaStream in a media element sees all the expected (deterministic) events even when not playing or autoplaying" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-firstframe.https.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-preload-none.https_1" purpose="Tests that loading a MediaStream in a media element eventually results in canplay even when not playing or autoplaying" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-preload-none.https_2" purpose="Test that preload 'none' is ignored for MediaStream used as srcObject for video" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_1" purpose="Tests that a MediaStream can be assigned to a video element with srcObject" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_2" purpose="Tests that a MediaStream assigned to a video element is not seekable" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_3" purpose="Tests that a MediaStream assigned to a video element is in readyState HAVE_NOTHING initially" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_4" purpose="Tests that a video element with a MediaStream assigned is not preloaded" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_5" purpose="Tests that a video element with a MediaStream assigned ignores playbackRate attributes (defaultPlaybackRate is identical)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_6" purpose="Tests that a video element with a MediaStream assigned ignores playbackRate attributes (defaultPlaybackRate is different)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_7" purpose="Tests that a media element with an assigned MediaStream reports the played attribute as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_8" purpose="Tests that a media element with an assigned MediaStream reports the currentTime attribute as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_9" purpose="Tests that a media element with an assigned MediaStream starts its timeline at 0 regardless of when the MediaStream was created" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-add-audio-track.https" purpose="Camera is not exposed in mediaDevices.enumerateDevices()" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-add-audio-track.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-audio-only.https" purpose="Tests that a MediaStream with exactly one audio track is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-audio-only.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-clone.https_1" purpose="Tests that cloning MediaStream objects works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-clone.https.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-clone.https_2" purpose="Tests that cloning MediaStreamTrack objects works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-clone.https.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-finished-add.https" purpose="Tests that adding a track to an inactive MediaStream is allowed" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-finished-add.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-gettrackid.https" purpose="Tests that MediaStream.getTrackById works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-gettrackid.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-id.https" purpose="Tests that a MediaStream with a correct id is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-id.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-idl.https" purpose="Tests that a MediaStream with a correct id is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-idl.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-removetrack.https_1" purpose="Tests that a removal from a MediaStream works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html?total_num=3&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-removetrack.https_2" purpose="Test that removal from a MediaStream fires ended on media elements (video first)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html?total_num=3&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-removetrack.https_3" purpose="Test that removal from a MediaStream fires ended on media elements (audio first)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html?total_num=3&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-supported-by-feature-policy_1" purpose="document.featurePolicy.features should advertise camera." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-supported-by-feature-policy.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-supported-by-feature-policy_2" purpose="document.featurePolicy.features should advertise microphone." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-supported-by-feature-policy.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-video-only.https" purpose="Tests that a MediaStream with a correct id is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-video-only.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-MediaElement-disabled-audio-is-silence.https" purpose="Tests that a disabled audio track in a MediaStream is rendered as silence" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-audio-is-silence.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-MediaElement-disabled-video-is-black.https" purpose="Tests that a disabled video track in a MediaStream is rendered as blackness" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-video-is-black.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_1" purpose="applyConstraints rejects invalid groupID" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_2" purpose="applyConstraints accepts invalid ideal groupID, does not change setting" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_3" purpose="applyConstraints rejects attempt to switch device using groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_4" purpose="applyConstraints rejects invalid resizeMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_5" purpose="applyConstraints accepts invalid ideal resizeMode, does not change setting" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-end-manual.https" purpose="Tests that MediaStreamTracks end properly on permission revocation" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-end-manual.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_1" purpose="Setup audio MediaStreamTrack getCapabilities() test for sampleRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_2" purpose="Setup audio MediaStreamTrack getCapabilities() test for sampleSize" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_3" purpose="Setup audio MediaStreamTrack getCapabilities() test for echoCancellation" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_4" purpose="Setup audio MediaStreamTrack getCapabilities() test for autoGainControl" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_5" purpose="Setup audio MediaStreamTrack getCapabilities() test for noiseSuppression" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_6" purpose="Setup audio MediaStreamTrack getCapabilities() test for latency" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_7" purpose="Setup audio MediaStreamTrack getCapabilities() test for channelCount" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_8" purpose="Setup audio MediaStreamTrack getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_9" purpose="Setup audio MediaStreamTrack getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_10" purpose="Setup video MediaStreamTrack getCapabilities() test for width" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=10</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_11" purpose="Setup video MediaStreamTrack getCapabilities() test for height" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=11</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_12" purpose="Setup video MediaStreamTrack getCapabilities() test for aspectRatio" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=12</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_13" purpose="Setup video MediaStreamTrack getCapabilities() test for frameRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=13</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_14" purpose="Setup video MediaStreamTrack getCapabilities() test for facingMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=14</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_15" purpose="Setup video MediaStreamTrack getCapabilities() test for resizeMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=15</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_16" purpose="Setup video MediaStreamTrack getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=16</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_17" purpose="Setup video MediaStreamTrack getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=17</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_18" purpose="Setup audio InputDeviceInfo getCapabilities() test for sampleRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=18</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_19" purpose="Setup audio InputDeviceInfo getCapabilities() test for sampleSize" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=19</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_20" purpose="Setup audio InputDeviceInfo getCapabilities() test for echoCancellation" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=20</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_21" purpose="Setup audio InputDeviceInfo getCapabilities() test for autoGainControl" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=21</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_22" purpose="Setup audio InputDeviceInfo getCapabilities() test for noiseSuppression" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=22</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_23" purpose="Setup audio InputDeviceInfo getCapabilities() test for latency" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=23</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_24" purpose="Setup audio InputDeviceInfo getCapabilities() test for channelCount" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=24</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_25" purpose="Setup audio InputDeviceInfo getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=25</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_26" purpose="Setup audio InputDeviceInfo getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=26</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_27" purpose="Setup video InputDeviceInfo getCapabilities() test for width" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=27</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_28" purpose="Setup video InputDeviceInfo getCapabilities() test for height" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=28</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_29" purpose="Setup video InputDeviceInfo getCapabilities() test for aspectRatio" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=29</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_30" purpose="Setup video InputDeviceInfo getCapabilities() test for frameRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=30</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_31" purpose="Setup video InputDeviceInfo getCapabilities() test for facingMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=31</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_32" purpose="Setup video InputDeviceInfo getCapabilities() test for resizeMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=32</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_33" purpose="Setup video InputDeviceInfo getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=33</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_34" purpose="Setup video InputDeviceInfo getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=34</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_1" purpose="A device can be opened twice and have the same device ID" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_2" purpose="A device can be opened twice with different resolutions requested" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_3" purpose="groupId is correctly reported by getSettings() for all input devices" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_4" purpose="deviceId is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_5" purpose="groupId is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_6" purpose="sampleRate is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_7" purpose="sampleSize is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_8" purpose="echoCancellation is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_9" purpose="autoGainControl is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_10" purpose="noiseSuppression is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=10</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_11" purpose="latency is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=11</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_12" purpose="channelCount is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=12</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_13" purpose="deviceId is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=13</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_14" purpose="groupId is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=14</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_15" purpose="width is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=15</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_16" purpose="height is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=16</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_17" purpose="aspectRatio is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=17</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_18" purpose="frameRate is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=18</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_19" purpose="facingMode is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=19</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_20" purpose="resizeMode is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=20</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_21" purpose="Stopped tracks should expose deviceId/groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=21</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-id.https" purpose="Tests that distinct mediastream tracks have distinct ids" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-id.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-init.https" purpose="getUserMedia({video:true}) creates a stream with a properly initialized video track" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-init.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrackEvent-constructor.https_1" purpose="The eventInitDict argument is required" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html?total_num=3&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrackEvent-constructor.https_2" purpose="The eventInitDict's track member is required." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html?total_num=3&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrackEvent-constructor.https_3" purpose="The MediaStreamTrackEvent instance's track attribute is set." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html?total_num=3&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_1" purpose="navigator.mozGetUserMedia should not exist" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_2" purpose="Passing MediaStream to URL.createObjectURL() should throw" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_3" purpose="MediaStream.onactive should not exist" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_4" purpose="MediaStream.oninactive should not exist" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+    </set>
+    <set name="MediaCapture_streams_VD_TV" type="js">
+      <capabilities>
+        <capability name="http://tizen.org/feature/profile"><value>TV</value></capability>
+        <capability name="http://tizen.org/feature/microphone"></capability>
+      </capabilities>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-api.https" purpose="mediaDevices.getUserMedia() is present on navigator" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-api.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-empty-option-param.https" purpose="Tests that getUserMedia is rejected with a TypeError when used with an empty options parameter" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-empty-option-param.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-impossible-constraint.https" purpose="Tests that setting an impossible constraint in getUserMedia fails" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-impossible-constraint.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-invalid-facing-mode.https" purpose="Tests that setting an invalid facingMode constraint in getUserMedia fails" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-invalid-facing-mode.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_1" purpose="Test that setting video-only valid constraints inside of audio is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_2" purpose="Test that setting video-only invalid constraints inside of audio is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_3" purpose="Test that setting audio-only valid constraints inside of video is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_4" purpose="Test that setting audio-only invalid constraints inside of video is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-optional-constraint.https" purpose="Tests that setting an optional constraint in getUserMedia is handled as optional" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-optional-constraint.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-required-constraint-with-ideal-value" purpose="Tests that setting a required constraint with an ideal value in getUserMedia works" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-required-constraint-with-ideal-value.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-trivial-constraint.https" purpose="Tests that setting a trivial mandatory constraint in getUserMedia works" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-trivial-constraint.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-unknownkey-option-param.https" purpose="Tests that getUserMedia is rejected with a TypeError when used with an unknown constraint" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-unknownkey-option-param.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-enumerateDevices-returned-objects.https" purpose="enumerateDevices returns expected objects in case device-info permission is granted" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices-returned-objects.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-enumerateDevices.https" purpose="InputDeviceInfo is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_1" purpose="navigator.mediaDevices.getSupportedConstraints exists" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_2" purpose="width is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_3" purpose="height is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_4" purpose="aspectRatio is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_5" purpose="frameRate is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_6" purpose="facingMode is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_7" purpose="resizeMode is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_8" purpose="sampleRate is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_9" purpose="sampleSize is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_10" purpose="echoCancellation is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=10</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_11" purpose="autoGainControl is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=11</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_12" purpose="noiseSuppression is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=12</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_13" purpose="latency is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=13</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_14" purpose="channelCount is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=14</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_15" purpose="deviceId is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=15</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_16" purpose="groupId is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=16</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_1" purpose="mediaDevices.getUserMedia() is present on navigator" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_2" purpose="groupId is correctly supported by getUserMedia() for video devices" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_3" purpose="groupId is correctly supported by getUserMedia() for audio devices" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_4" purpose="getUserMedia() supports setting none as resizeMode." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_5" purpose="getUserMedia() supports setting crop-and-scale as resizeMode." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_6" purpose="getUserMedia() fails with exact invalid resizeMode." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-firstframe.https_1" purpose="Tests that loading a MediaStream in a media element eventually results in canplay even when not playing or autoplaying" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-firstframe.https.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-firstframe.https_2" purpose="Tests that loading a MediaStream in a media element sees all the expected (deterministic) events even when not playing or autoplaying" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-firstframe.https.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-preload-none.https_1" purpose="Tests that loading a MediaStream in a media element eventually results in canplay even when not playing or autoplaying" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-preload-none.https_2" purpose="Test that preload 'none' is ignored for MediaStream used as srcObject for video" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_1" purpose="Tests that a MediaStream can be assigned to a video element with srcObject" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_2" purpose="Tests that a MediaStream assigned to a video element is not seekable" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_3" purpose="Tests that a MediaStream assigned to a video element is in readyState HAVE_NOTHING initially" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_4" purpose="Tests that a video element with a MediaStream assigned is not preloaded" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_5" purpose="Tests that a video element with a MediaStream assigned ignores playbackRate attributes (defaultPlaybackRate is identical)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_6" purpose="Tests that a video element with a MediaStream assigned ignores playbackRate attributes (defaultPlaybackRate is different)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_7" purpose="Tests that a media element with an assigned MediaStream reports the played attribute as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_8" purpose="Tests that a media element with an assigned MediaStream reports the currentTime attribute as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_9" purpose="Tests that a media element with an assigned MediaStream starts its timeline at 0 regardless of when the MediaStream was created" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-add-audio-track.https" purpose="Camera is not exposed in mediaDevices.enumerateDevices()" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-add-audio-track.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-audio-only.https" purpose="Tests that a MediaStream with exactly one audio track is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-audio-only.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-clone.https_1" purpose="Tests that cloning MediaStream objects works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-clone.https.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-clone.https_2" purpose="Tests that cloning MediaStreamTrack objects works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-clone.https.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-finished-add.https" purpose="Tests that adding a track to an inactive MediaStream is allowed" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-finished-add.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-gettrackid.https" purpose="Tests that MediaStream.getTrackById works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-gettrackid.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-id.https" purpose="Tests that a MediaStream with a correct id is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-id.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-idl.https" purpose="Tests that a MediaStream with a correct id is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-idl.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-removetrack.https_1" purpose="Tests that a removal from a MediaStream works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html?total_num=3&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-removetrack.https_2" purpose="Test that removal from a MediaStream fires ended on media elements (video first)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html?total_num=3&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-removetrack.https_3" purpose="Test that removal from a MediaStream fires ended on media elements (audio first)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html?total_num=3&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-supported-by-feature-policy_1" purpose="document.featurePolicy.features should advertise camera." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-supported-by-feature-policy.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-supported-by-feature-policy_2" purpose="document.featurePolicy.features should advertise microphone." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-supported-by-feature-policy.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-video-only.https" purpose="Tests that a MediaStream with a correct id is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-video-only.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-MediaElement-disabled-audio-is-silence.https" purpose="Tests that a disabled audio track in a MediaStream is rendered as silence" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-audio-is-silence.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-MediaElement-disabled-video-is-black.https" purpose="Tests that a disabled video track in a MediaStream is rendered as blackness" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-video-is-black.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_1" purpose="applyConstraints rejects invalid groupID" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_2" purpose="applyConstraints accepts invalid ideal groupID, does not change setting" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_3" purpose="applyConstraints rejects attempt to switch device using groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_4" purpose="applyConstraints rejects invalid resizeMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_5" purpose="applyConstraints accepts invalid ideal resizeMode, does not change setting" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-end-manual.https" purpose="Tests that MediaStreamTracks end properly on permission revocation" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-end-manual.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_1" purpose="Setup audio MediaStreamTrack getCapabilities() test for sampleRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_2" purpose="Setup audio MediaStreamTrack getCapabilities() test for sampleSize" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_3" purpose="Setup audio MediaStreamTrack getCapabilities() test for echoCancellation" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_4" purpose="Setup audio MediaStreamTrack getCapabilities() test for autoGainControl" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_5" purpose="Setup audio MediaStreamTrack getCapabilities() test for noiseSuppression" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_6" purpose="Setup audio MediaStreamTrack getCapabilities() test for latency" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_7" purpose="Setup audio MediaStreamTrack getCapabilities() test for channelCount" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_8" purpose="Setup audio MediaStreamTrack getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_9" purpose="Setup audio MediaStreamTrack getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_10" purpose="Setup video MediaStreamTrack getCapabilities() test for width" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=10</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_11" purpose="Setup video MediaStreamTrack getCapabilities() test for height" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=11</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_12" purpose="Setup video MediaStreamTrack getCapabilities() test for aspectRatio" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=12</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_13" purpose="Setup video MediaStreamTrack getCapabilities() test for frameRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=13</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_14" purpose="Setup video MediaStreamTrack getCapabilities() test for facingMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=14</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_15" purpose="Setup video MediaStreamTrack getCapabilities() test for resizeMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=15</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_16" purpose="Setup video MediaStreamTrack getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=16</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_17" purpose="Setup video MediaStreamTrack getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=17</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_18" purpose="Setup audio InputDeviceInfo getCapabilities() test for sampleRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=18</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_19" purpose="Setup audio InputDeviceInfo getCapabilities() test for sampleSize" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=19</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_20" purpose="Setup audio InputDeviceInfo getCapabilities() test for echoCancellation" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=20</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_21" purpose="Setup audio InputDeviceInfo getCapabilities() test for autoGainControl" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=21</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_22" purpose="Setup audio InputDeviceInfo getCapabilities() test for noiseSuppression" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=22</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_23" purpose="Setup audio InputDeviceInfo getCapabilities() test for latency" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=23</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_24" purpose="Setup audio InputDeviceInfo getCapabilities() test for channelCount" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=24</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_25" purpose="Setup audio InputDeviceInfo getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=25</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_26" purpose="Setup audio InputDeviceInfo getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=26</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_27" purpose="Setup video InputDeviceInfo getCapabilities() test for width" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=27</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_28" purpose="Setup video InputDeviceInfo getCapabilities() test for height" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=28</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_29" purpose="Setup video InputDeviceInfo getCapabilities() test for aspectRatio" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=29</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_30" purpose="Setup video InputDeviceInfo getCapabilities() test for frameRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=30</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_31" purpose="Setup video InputDeviceInfo getCapabilities() test for facingMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=31</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_32" purpose="Setup video InputDeviceInfo getCapabilities() test for resizeMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=32</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_33" purpose="Setup video InputDeviceInfo getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=33</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_34" purpose="Setup video InputDeviceInfo getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=34</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_1" purpose="A device can be opened twice and have the same device ID" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_2" purpose="A device can be opened twice with different resolutions requested" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_3" purpose="groupId is correctly reported by getSettings() for all input devices" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_4" purpose="deviceId is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_5" purpose="groupId is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_6" purpose="sampleRate is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_7" purpose="sampleSize is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_8" purpose="echoCancellation is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_9" purpose="autoGainControl is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_10" purpose="noiseSuppression is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=10</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_11" purpose="latency is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=11</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_12" purpose="channelCount is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=12</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_13" purpose="deviceId is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=13</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_14" purpose="groupId is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=14</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_15" purpose="width is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=15</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_16" purpose="height is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=16</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_17" purpose="aspectRatio is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=17</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_18" purpose="frameRate is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=18</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_19" purpose="facingMode is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=19</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_20" purpose="resizeMode is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=20</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_21" purpose="Stopped tracks should expose deviceId/groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=21</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-id.https" purpose="Tests that distinct mediastream tracks have distinct ids" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-id.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-init.https" purpose="getUserMedia({video:true}) creates a stream with a properly initialized video track" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-init.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrackEvent-constructor.https_1" purpose="The eventInitDict argument is required" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html?total_num=3&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrackEvent-constructor.https_2" purpose="The eventInitDict's track member is required." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html?total_num=3&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrackEvent-constructor.https_3" purpose="The MediaStreamTrackEvent instance's track attribute is set." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html?total_num=3&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_1" purpose="navigator.mozGetUserMedia should not exist" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_2" purpose="Passing MediaStream to URL.createObjectURL() should throw" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_3" purpose="MediaStream.onactive should not exist" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_4" purpose="MediaStream.oninactive should not exist" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+    </set>
   </suite>
 </test_definition>
index 5c6aeb44bc858981c660b7bc5f07b139362941b2..c0f6dc068540ba53fc5b9a895dec4f4a96174c84 100755 (executable)
@@ -5,8 +5,6 @@
     <set name="MediaCapture" type="js">
       <capabilities>
         <capability name="http://tizen.org/feature/camera"/>
-      </capabilities>
-      <capabilities>
         <capability name="http://tizen.org/feature/microphone"/>
       </capabilities>
       <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="HTMLInputElement_exist" purpose="Check if the interface HTMLInputElement exists">
         <description>
           <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/capture_reflect.html?total_num=2&amp;amp;locator_key=id&amp;amp;value=2</test_script_entry>
         </description>
-        </testcase>
-   <!--  <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="manual" id="capture_video" purpose="Check if the input will accept an video when capture set to acmcorder">
+      </testcase>
+    </set>
+    <set name="MediaCapture_streams_IOT" type="js">
+      <capabilities>
+        <capability name="http://tizen.org/feature/profile"><value>COMMON</value></capability>
+      </capabilities>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-api.https" purpose="mediaDevices.getUserMedia() is present on navigator" onload_delay="90">
         <description>
-          <steps>
-            <pre_condition/>
-            <step order="1">
-              <step_desc>Click the button, allow microphone and camera access, and start to capture a video</step_desc>
-              <expected>There appears a video capture screen, there is a way to disable the audio record, and there is a way to stop the video capture.</expected>
-            </step>
-          </steps>
-          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/capture_video.html</test_script_entry>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-api.https.html</test_script_entry>
         </description>
-        </testcase>
-        <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="manual" id="capture_fallback_file_upload" purpose="Check if the input will can accept an file as expected when accept is set to a MIME type">
-        <description>
-          <steps>
-            <pre_condition/>
-            <step order="1">
-              <step_desc>Download upload.doc to local, then Select the local upload.doc file.</step_desc>
-              <expected>Pass</expected>
-            </step>
-          </steps>
-          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/capture_fallback_file_upload.html</test_script_entry>
-        </description>
-        </testcase> -->
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-empty-option-param.https" purpose="Tests that getUserMedia is rejected with a TypeError when used with an empty options parameter" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-empty-option-param.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-impossible-constraint.https" purpose="Tests that setting an impossible constraint in getUserMedia fails" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-impossible-constraint.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-invalid-facing-mode.https" purpose="Tests that setting an invalid facingMode constraint in getUserMedia fails" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-invalid-facing-mode.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_1" purpose="Test that setting video-only valid constraints inside of audio is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_2" purpose="Test that setting video-only invalid constraints inside of audio is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_3" purpose="Test that setting audio-only valid constraints inside of video is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_4" purpose="Test that setting audio-only invalid constraints inside of video is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-optional-constraint.https" purpose="Tests that setting an optional constraint in getUserMedia is handled as optional" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-optional-constraint.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-required-constraint-with-ideal-value" purpose="Tests that setting a required constraint with an ideal value in getUserMedia works" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-required-constraint-with-ideal-value.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-trivial-constraint.https" purpose="Tests that setting a trivial mandatory constraint in getUserMedia works" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-trivial-constraint.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-unknownkey-option-param.https" purpose="Tests that getUserMedia is rejected with a TypeError when used with an unknown constraint" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-unknownkey-option-param.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-enumerateDevices-returned-objects.https" purpose="enumerateDevices returns expected objects in case device-info permission is granted" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices-returned-objects.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-enumerateDevices.https" purpose="InputDeviceInfo is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_1" purpose="navigator.mediaDevices.getSupportedConstraints exists" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_2" purpose="width is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_3" purpose="height is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_4" purpose="aspectRatio is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_5" purpose="frameRate is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_6" purpose="facingMode is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_7" purpose="resizeMode is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_8" purpose="sampleRate is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_9" purpose="sampleSize is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_10" purpose="echoCancellation is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=10</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_11" purpose="autoGainControl is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=11</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_12" purpose="noiseSuppression is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=12</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_13" purpose="latency is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=13</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_14" purpose="channelCount is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=14</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_15" purpose="deviceId is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=15</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_16" purpose="groupId is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=16</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_1" purpose="mediaDevices.getUserMedia() is present on navigator" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_2" purpose="groupId is correctly supported by getUserMedia() for video devices" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_3" purpose="groupId is correctly supported by getUserMedia() for audio devices" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_4" purpose="getUserMedia() supports setting none as resizeMode." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_5" purpose="getUserMedia() supports setting crop-and-scale as resizeMode." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_6" purpose="getUserMedia() fails with exact invalid resizeMode." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-firstframe.https_1" purpose="Tests that loading a MediaStream in a media element eventually results in canplay even when not playing or autoplaying" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-firstframe.https.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-firstframe.https_2" purpose="Tests that loading a MediaStream in a media element sees all the expected (deterministic) events even when not playing or autoplaying" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-firstframe.https.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-preload-none.https_1" purpose="Tests that loading a MediaStream in a media element eventually results in canplay even when not playing or autoplaying" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-preload-none.https_2" purpose="Test that preload 'none' is ignored for MediaStream used as srcObject for video" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_1" purpose="Tests that a MediaStream can be assigned to a video element with srcObject" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_2" purpose="Tests that a MediaStream assigned to a video element is not seekable" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_3" purpose="Tests that a MediaStream assigned to a video element is in readyState HAVE_NOTHING initially" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_4" purpose="Tests that a video element with a MediaStream assigned is not preloaded" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_5" purpose="Tests that a video element with a MediaStream assigned ignores playbackRate attributes (defaultPlaybackRate is identical)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_6" purpose="Tests that a video element with a MediaStream assigned ignores playbackRate attributes (defaultPlaybackRate is different)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_7" purpose="Tests that a media element with an assigned MediaStream reports the played attribute as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_8" purpose="Tests that a media element with an assigned MediaStream reports the currentTime attribute as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_9" purpose="Tests that a media element with an assigned MediaStream starts its timeline at 0 regardless of when the MediaStream was created" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-add-audio-track.https" purpose="Camera is not exposed in mediaDevices.enumerateDevices()" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-add-audio-track.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-audio-only.https" purpose="Tests that a MediaStream with exactly one audio track is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-audio-only.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-clone.https_1" purpose="Tests that cloning MediaStream objects works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-clone.https.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-clone.https_2" purpose="Tests that cloning MediaStreamTrack objects works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-clone.https.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-finished-add.https" purpose="Tests that adding a track to an inactive MediaStream is allowed" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-finished-add.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-gettrackid.https" purpose="Tests that MediaStream.getTrackById works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-gettrackid.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-id.https" purpose="Tests that a MediaStream with a correct id is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-id.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-idl.https" purpose="Tests that a MediaStream with a correct id is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-idl.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-removetrack.https_1" purpose="Tests that a removal from a MediaStream works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html?total_num=3&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-removetrack.https_2" purpose="Test that removal from a MediaStream fires ended on media elements (video first)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html?total_num=3&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-removetrack.https_3" purpose="Test that removal from a MediaStream fires ended on media elements (audio first)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html?total_num=3&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-supported-by-feature-policy_1" purpose="document.featurePolicy.features should advertise camera." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-supported-by-feature-policy.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-supported-by-feature-policy_2" purpose="document.featurePolicy.features should advertise microphone." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-supported-by-feature-policy.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-video-only.https" purpose="Tests that a MediaStream with a correct id is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-video-only.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-MediaElement-disabled-audio-is-silence.https" purpose="Tests that a disabled audio track in a MediaStream is rendered as silence" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-audio-is-silence.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-MediaElement-disabled-video-is-black.https" purpose="Tests that a disabled video track in a MediaStream is rendered as blackness" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-video-is-black.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_1" purpose="applyConstraints rejects invalid groupID" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_2" purpose="applyConstraints accepts invalid ideal groupID, does not change setting" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_3" purpose="applyConstraints rejects attempt to switch device using groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_4" purpose="applyConstraints rejects invalid resizeMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_5" purpose="applyConstraints accepts invalid ideal resizeMode, does not change setting" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-end-manual.https" purpose="Tests that MediaStreamTracks end properly on permission revocation" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-end-manual.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_1" purpose="Setup audio MediaStreamTrack getCapabilities() test for sampleRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_2" purpose="Setup audio MediaStreamTrack getCapabilities() test for sampleSize" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_3" purpose="Setup audio MediaStreamTrack getCapabilities() test for echoCancellation" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_4" purpose="Setup audio MediaStreamTrack getCapabilities() test for autoGainControl" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_5" purpose="Setup audio MediaStreamTrack getCapabilities() test for noiseSuppression" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_6" purpose="Setup audio MediaStreamTrack getCapabilities() test for latency" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_7" purpose="Setup audio MediaStreamTrack getCapabilities() test for channelCount" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_8" purpose="Setup audio MediaStreamTrack getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_9" purpose="Setup audio MediaStreamTrack getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_10" purpose="Setup video MediaStreamTrack getCapabilities() test for width" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=10</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_11" purpose="Setup video MediaStreamTrack getCapabilities() test for height" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=11</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_12" purpose="Setup video MediaStreamTrack getCapabilities() test for aspectRatio" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=12</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_13" purpose="Setup video MediaStreamTrack getCapabilities() test for frameRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=13</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_14" purpose="Setup video MediaStreamTrack getCapabilities() test for facingMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=14</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_15" purpose="Setup video MediaStreamTrack getCapabilities() test for resizeMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=15</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_16" purpose="Setup video MediaStreamTrack getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=16</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_17" purpose="Setup video MediaStreamTrack getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=17</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_18" purpose="Setup audio InputDeviceInfo getCapabilities() test for sampleRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=18</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_19" purpose="Setup audio InputDeviceInfo getCapabilities() test for sampleSize" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=19</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_20" purpose="Setup audio InputDeviceInfo getCapabilities() test for echoCancellation" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=20</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_21" purpose="Setup audio InputDeviceInfo getCapabilities() test for autoGainControl" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=21</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_22" purpose="Setup audio InputDeviceInfo getCapabilities() test for noiseSuppression" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=22</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_23" purpose="Setup audio InputDeviceInfo getCapabilities() test for latency" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=23</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_24" purpose="Setup audio InputDeviceInfo getCapabilities() test for channelCount" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=24</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_25" purpose="Setup audio InputDeviceInfo getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=25</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_26" purpose="Setup audio InputDeviceInfo getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=26</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_27" purpose="Setup video InputDeviceInfo getCapabilities() test for width" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=27</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_28" purpose="Setup video InputDeviceInfo getCapabilities() test for height" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=28</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_29" purpose="Setup video InputDeviceInfo getCapabilities() test for aspectRatio" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=29</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_30" purpose="Setup video InputDeviceInfo getCapabilities() test for frameRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=30</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_31" purpose="Setup video InputDeviceInfo getCapabilities() test for facingMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=31</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_32" purpose="Setup video InputDeviceInfo getCapabilities() test for resizeMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=32</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_33" purpose="Setup video InputDeviceInfo getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=33</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_34" purpose="Setup video InputDeviceInfo getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=34</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_1" purpose="A device can be opened twice and have the same device ID" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_2" purpose="A device can be opened twice with different resolutions requested" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_3" purpose="groupId is correctly reported by getSettings() for all input devices" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_4" purpose="deviceId is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_5" purpose="groupId is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_6" purpose="sampleRate is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_7" purpose="sampleSize is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_8" purpose="echoCancellation is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_9" purpose="autoGainControl is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_10" purpose="noiseSuppression is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=10</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_11" purpose="latency is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=11</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_12" purpose="channelCount is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=12</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_13" purpose="deviceId is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=13</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_14" purpose="groupId is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=14</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_15" purpose="width is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=15</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_16" purpose="height is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=16</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_17" purpose="aspectRatio is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=17</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_18" purpose="frameRate is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=18</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_19" purpose="facingMode is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=19</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_20" purpose="resizeMode is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=20</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_21" purpose="Stopped tracks should expose deviceId/groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=21</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-id.https" purpose="Tests that distinct mediastream tracks have distinct ids" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-id.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-init.https" purpose="getUserMedia({video:true}) creates a stream with a properly initialized video track" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-init.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrackEvent-constructor.https_1" purpose="The eventInitDict argument is required" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html?total_num=3&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrackEvent-constructor.https_2" purpose="The eventInitDict's track member is required." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html?total_num=3&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrackEvent-constructor.https_3" purpose="The MediaStreamTrackEvent instance's track attribute is set." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html?total_num=3&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_1" purpose="navigator.mozGetUserMedia should not exist" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_2" purpose="Passing MediaStream to URL.createObjectURL() should throw" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_3" purpose="MediaStream.onactive should not exist" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_4" purpose="MediaStream.oninactive should not exist" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+    </set>
+    <set name="MediaCapture_streams_VD_TV" type="js">
+      <capabilities>
+        <capability name="http://tizen.org/feature/profile"><value>TV</value></capability>
+        <capability name="http://tizen.org/feature/microphone"></capability>
+      </capabilities>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-api.https" purpose="mediaDevices.getUserMedia() is present on navigator" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-api.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-empty-option-param.https" purpose="Tests that getUserMedia is rejected with a TypeError when used with an empty options parameter" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-empty-option-param.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-impossible-constraint.https" purpose="Tests that setting an impossible constraint in getUserMedia fails" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-impossible-constraint.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-invalid-facing-mode.https" purpose="Tests that setting an invalid facingMode constraint in getUserMedia fails" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-invalid-facing-mode.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_1" purpose="Test that setting video-only valid constraints inside of audio is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_2" purpose="Test that setting video-only invalid constraints inside of audio is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_3" purpose="Test that setting audio-only valid constraints inside of video is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-non-applicable-constraint.https_4" purpose="Test that setting audio-only invalid constraints inside of video is simply ignored" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-non-applicable-constraint.https.html?total_num=4&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-optional-constraint.https" purpose="Tests that setting an optional constraint in getUserMedia is handled as optional" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-optional-constraint.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-required-constraint-with-ideal-value" purpose="Tests that setting a required constraint with an ideal value in getUserMedia works" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-required-constraint-with-ideal-value.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-trivial-constraint.https" purpose="Tests that setting a trivial mandatory constraint in getUserMedia works" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-trivial-constraint.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="GUM-unknownkey-option-param.https" purpose="Tests that getUserMedia is rejected with a TypeError when used with an unknown constraint" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/GUM-unknownkey-option-param.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-enumerateDevices-returned-objects.https" purpose="enumerateDevices returns expected objects in case device-info permission is granted" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices-returned-objects.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-enumerateDevices.https" purpose="InputDeviceInfo is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-enumerateDevices.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_1" purpose="navigator.mediaDevices.getSupportedConstraints exists" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_2" purpose="width is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_3" purpose="height is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_4" purpose="aspectRatio is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_5" purpose="frameRate is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_6" purpose="facingMode is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_7" purpose="resizeMode is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_8" purpose="sampleRate is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_9" purpose="sampleSize is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_10" purpose="echoCancellation is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=10</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_11" purpose="autoGainControl is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=11</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_12" purpose="noiseSuppression is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=12</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_13" purpose="latency is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=13</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_14" purpose="channelCount is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=14</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_15" purpose="deviceId is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=15</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getSupportedConstraints.https_16" purpose="groupId is supported" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getSupportedConstraints.https.html?total_num=16&amp;locator_key=id&amp;value=16</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_1" purpose="mediaDevices.getUserMedia() is present on navigator" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_2" purpose="groupId is correctly supported by getUserMedia() for video devices" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_3" purpose="groupId is correctly supported by getUserMedia() for audio devices" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_4" purpose="getUserMedia() supports setting none as resizeMode." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_5" purpose="getUserMedia() supports setting crop-and-scale as resizeMode." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaDevices-getUserMedia.https_6" purpose="getUserMedia() fails with exact invalid resizeMode." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaDevices-getUserMedia.https.html?total_num=6&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-firstframe.https_1" purpose="Tests that loading a MediaStream in a media element eventually results in canplay even when not playing or autoplaying" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-firstframe.https.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-firstframe.https_2" purpose="Tests that loading a MediaStream in a media element sees all the expected (deterministic) events even when not playing or autoplaying" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-firstframe.https.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-preload-none.https_1" purpose="Tests that loading a MediaStream in a media element eventually results in canplay even when not playing or autoplaying" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-preload-none.https_2" purpose="Test that preload 'none' is ignored for MediaStream used as srcObject for video" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-preload-none.https.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_1" purpose="Tests that a MediaStream can be assigned to a video element with srcObject" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_2" purpose="Tests that a MediaStream assigned to a video element is not seekable" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_3" purpose="Tests that a MediaStream assigned to a video element is in readyState HAVE_NOTHING initially" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_4" purpose="Tests that a video element with a MediaStream assigned is not preloaded" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_5" purpose="Tests that a video element with a MediaStream assigned ignores playbackRate attributes (defaultPlaybackRate is identical)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_6" purpose="Tests that a video element with a MediaStream assigned ignores playbackRate attributes (defaultPlaybackRate is different)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_7" purpose="Tests that a media element with an assigned MediaStream reports the played attribute as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_8" purpose="Tests that a media element with an assigned MediaStream reports the currentTime attribute as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-MediaElement-srcObject.https_9" purpose="Tests that a media element with an assigned MediaStream starts its timeline at 0 regardless of when the MediaStream was created" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-MediaElement-srcObject.https.html?total_num=9&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-add-audio-track.https" purpose="Camera is not exposed in mediaDevices.enumerateDevices()" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-add-audio-track.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-audio-only.https" purpose="Tests that a MediaStream with exactly one audio track is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-audio-only.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-clone.https_1" purpose="Tests that cloning MediaStream objects works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-clone.https.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-clone.https_2" purpose="Tests that cloning MediaStreamTrack objects works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-clone.https.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-finished-add.https" purpose="Tests that adding a track to an inactive MediaStream is allowed" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-finished-add.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-gettrackid.https" purpose="Tests that MediaStream.getTrackById works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-gettrackid.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-id.https" purpose="Tests that a MediaStream with a correct id is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-id.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-idl.https" purpose="Tests that a MediaStream with a correct id is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-idl.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-removetrack.https_1" purpose="Tests that a removal from a MediaStream works as expected" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html?total_num=3&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-removetrack.https_2" purpose="Test that removal from a MediaStream fires ended on media elements (video first)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html?total_num=3&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-removetrack.https_3" purpose="Test that removal from a MediaStream fires ended on media elements (audio first)" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-removetrack.https.html?total_num=3&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-supported-by-feature-policy_1" purpose="document.featurePolicy.features should advertise camera." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-supported-by-feature-policy.html?total_num=2&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-supported-by-feature-policy_2" purpose="document.featurePolicy.features should advertise microphone." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-supported-by-feature-policy.html?total_num=2&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStream-video-only.https" purpose="Tests that a MediaStream with a correct id is returned" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStream-video-only.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-MediaElement-disabled-audio-is-silence.https" purpose="Tests that a disabled audio track in a MediaStream is rendered as silence" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-audio-is-silence.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-MediaElement-disabled-video-is-black.https" purpose="Tests that a disabled video track in a MediaStream is rendered as blackness" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-MediaElement-disabled-video-is-black.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_1" purpose="applyConstraints rejects invalid groupID" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_2" purpose="applyConstraints accepts invalid ideal groupID, does not change setting" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_3" purpose="applyConstraints rejects attempt to switch device using groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_4" purpose="applyConstraints rejects invalid resizeMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-applyConstraints.https_5" purpose="applyConstraints accepts invalid ideal resizeMode, does not change setting" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-applyConstraints.https.html?total_num=5&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-end-manual.https" purpose="Tests that MediaStreamTracks end properly on permission revocation" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-end-manual.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_1" purpose="Setup audio MediaStreamTrack getCapabilities() test for sampleRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_2" purpose="Setup audio MediaStreamTrack getCapabilities() test for sampleSize" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_3" purpose="Setup audio MediaStreamTrack getCapabilities() test for echoCancellation" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_4" purpose="Setup audio MediaStreamTrack getCapabilities() test for autoGainControl" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_5" purpose="Setup audio MediaStreamTrack getCapabilities() test for noiseSuppression" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_6" purpose="Setup audio MediaStreamTrack getCapabilities() test for latency" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_7" purpose="Setup audio MediaStreamTrack getCapabilities() test for channelCount" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_8" purpose="Setup audio MediaStreamTrack getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_9" purpose="Setup audio MediaStreamTrack getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_10" purpose="Setup video MediaStreamTrack getCapabilities() test for width" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=10</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_11" purpose="Setup video MediaStreamTrack getCapabilities() test for height" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=11</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_12" purpose="Setup video MediaStreamTrack getCapabilities() test for aspectRatio" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=12</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_13" purpose="Setup video MediaStreamTrack getCapabilities() test for frameRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=13</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_14" purpose="Setup video MediaStreamTrack getCapabilities() test for facingMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=14</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_15" purpose="Setup video MediaStreamTrack getCapabilities() test for resizeMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=15</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_16" purpose="Setup video MediaStreamTrack getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=16</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_17" purpose="Setup video MediaStreamTrack getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=17</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_18" purpose="Setup audio InputDeviceInfo getCapabilities() test for sampleRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=18</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_19" purpose="Setup audio InputDeviceInfo getCapabilities() test for sampleSize" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=19</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_20" purpose="Setup audio InputDeviceInfo getCapabilities() test for echoCancellation" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=20</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_21" purpose="Setup audio InputDeviceInfo getCapabilities() test for autoGainControl" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=21</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_22" purpose="Setup audio InputDeviceInfo getCapabilities() test for noiseSuppression" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=22</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_23" purpose="Setup audio InputDeviceInfo getCapabilities() test for latency" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=23</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_24" purpose="Setup audio InputDeviceInfo getCapabilities() test for channelCount" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=24</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_25" purpose="Setup audio InputDeviceInfo getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=25</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_26" purpose="Setup audio InputDeviceInfo getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=26</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_27" purpose="Setup video InputDeviceInfo getCapabilities() test for width" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=27</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_28" purpose="Setup video InputDeviceInfo getCapabilities() test for height" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=28</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_29" purpose="Setup video InputDeviceInfo getCapabilities() test for aspectRatio" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=29</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_30" purpose="Setup video InputDeviceInfo getCapabilities() test for frameRate" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=30</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_31" purpose="Setup video InputDeviceInfo getCapabilities() test for facingMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=31</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_32" purpose="Setup video InputDeviceInfo getCapabilities() test for resizeMode" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=32</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_33" purpose="Setup video InputDeviceInfo getCapabilities() test for deviceId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=33</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getCapabilities.https_34" purpose="Setup video InputDeviceInfo getCapabilities() test for groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getCapabilities.https.html?total_num=34&amp;locator_key=id&amp;value=34</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_1" purpose="A device can be opened twice and have the same device ID" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_2" purpose="A device can be opened twice with different resolutions requested" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_3" purpose="groupId is correctly reported by getSettings() for all input devices" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_4" purpose="deviceId is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_5" purpose="groupId is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=5</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_6" purpose="sampleRate is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=6</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_7" purpose="sampleSize is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=7</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_8" purpose="echoCancellation is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=8</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_9" purpose="autoGainControl is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=9</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_10" purpose="noiseSuppression is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=10</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_11" purpose="latency is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=11</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_12" purpose="channelCount is reported by getSettings() for getUserMedia() audio tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=12</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_13" purpose="deviceId is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=13</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_14" purpose="groupId is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=14</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_15" purpose="width is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=15</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_16" purpose="height is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=16</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_17" purpose="aspectRatio is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=17</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_18" purpose="frameRate is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=18</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_19" purpose="facingMode is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=19</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_20" purpose="resizeMode is reported by getSettings() for getUserMedia() video tracks" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=20</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-getSettings.https_21" purpose="Stopped tracks should expose deviceId/groupId" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-getSettings.https.html?total_num=21&amp;locator_key=id&amp;value=21</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-id.https" purpose="Tests that distinct mediastream tracks have distinct ids" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-id.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrack-init.https" purpose="getUserMedia({video:true}) creates a stream with a properly initialized video track" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrack-init.https.html</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrackEvent-constructor.https_1" purpose="The eventInitDict argument is required" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html?total_num=3&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrackEvent-constructor.https_2" purpose="The eventInitDict's track member is required." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html?total_num=3&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="MediaStreamTrackEvent-constructor.https_3" purpose="The MediaStreamTrackEvent instance's track attribute is set." onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/MediaStreamTrackEvent-constructor.https.html?total_num=3&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_1" purpose="navigator.mozGetUserMedia should not exist" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=1</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_2" purpose="Passing MediaStream to URL.createObjectURL() should throw" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=2</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_3" purpose="MediaStream.onactive should not exist" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=3</test_script_entry>
+        </description>
+      </testcase>
+      <testcase component="W3C_HTML5 APIs/Media/HTML Media Capture" execution_type="auto" id="historical.https_4" purpose="MediaStream.oninactive should not exist" onload_delay="90">
+        <description>
+          <test_script_entry>/opt/tct-mediacapture-w3c-tests/mediacapture/w3c/mediacapture-streams/historical.https.html?total_num=4&amp;locator_key=id&amp;value=4</test_script_entry>
+        </description>
+      </testcase>
     </set>
   </suite>
 </test_definition>