Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[webrtc] Use procedurally-generated media streams #10746

Merged
merged 5 commits into from
Jul 7, 2018

Conversation

jugglinmike
Copy link
Contributor

@jugglinmike jugglinmike commented May 2, 2018

When scripted to rely on getUserMedia, tests for WebRTC require human
interaction and physical capture devices. Some browsers offer features
to circumvent these requirements, but such functionality is non-standard
and subverts code paths used in real-world settings.

Create media streams algorithmically in order to eliminate the
dependency on the getUserMedia API and better facilitate automation.


This patch is based on a suggestion from @jan-ivar in gh-7424, who wrote:

FWIW it should be simple to whip up fake audio tracks using web audio. See my old blog.

It does not rely on any out-of-band configuration, implementation-specific code, or standardized test APIs (e.g. new WebDriver commands). In other words, the tests are more portable, more realistic, and require less effort to execute. For these reasons, it seems preferable to the getUserMedia-based approach implemented in master today.

It may be that I'm missing a detail of WebRTC that makes this approach inappropriate. Anecdotally, it improves results for both Chromium and Firefox when run in automation. Here's how this patch affects test results in Chromium and Firefox Nightly (additional details about testing procedure are included below):

Browser On `master` using fake streams With patch to use generated streams
Chromium
Ran 709 checks (80 tests, 629 subtests)
Expected results: 291
Unexpected results: 418
  test: 3 (3 timeout)
  subtest: 415 (411 fail, 4 timeout)
Ran 709 checks (80 tests, 629 subtests)
Expected results: 291
Unexpected results: 418
  test: 3 (3 timeout)
  subtest: 415 (411 fail, 4 timeout)
Firefox Nightly
Ran 534 checks (80 tests, 454 subtests)
Expected results: 252
Unexpected results: 282
  test: 22 (22 timeout)
  subtest: 260 (259 fail, 1 timeout)
Ran 658 checks (80 tests, 578 subtests)
Expected results: 340
Unexpected results: 318
  test: 7 (7 timeout)
  subtest: 311 (311 fail)

This data does not communicate changes in the subtest failures themselves. In that regard, the patch may improve things for Chromium because the error messages more clearly relate to the feature under test. I've included a comparison of the "WPT report" for both Chromium and Firefox below. (This is the data structure generated by the WPT CLI via the --log-wptreport options.)

It is more difficult for me to validate the change in WebKit/Safari, though there may be a problem using this approach there. In gh-7424, @youennf wrote:

For WebKit, captureStream would be an option but ICE candidate filtering would probably make the test fail.

Naively, I expected such filtering would interfere with a getUserMedia-based solution, as well (since it concerns the candidate's IP address, not the way the media stream). @youennf: could you elaborate?

Testing environment
  • System: Ubuntu 16.04, 64-bit
  • WPT revision: fe5aaaf
  • Chromium:
    • version: Chromium 65.0.3325.181 Built on Ubuntu , running on Ubuntu 16.04
    • WPT flags specified when running from the master branch: --binary-arg=--use-fake-ui-for-media-stream --binary-arg=--use-fake-device-for-media-stream
  • Firefox:
    • version: Mozilla Firefox 61.0a1
    • WPT flags specified when running from the master branch: --setpref 'media.navigator.streams.fake=true' --setpref 'media.navigator.permission.disabled=true'

All WPT CLI processes were run via xvfb-run --auto-servernum to simulate a "headless" testing environment. The browser-specific configuration was informed by a recent discussion on collecting WebRTC results for https://wpt.fyi: web-platform-tests/results-collection#125

Comparison of Chromium reports
diff --git a/wptreport-chrome-master.json b/wptreport-chrome-patch.json
index 5041062..6589103 100644
--- a/wptreport-chrome-master.json
+++ b/wptreport-chrome-patch.json
@@ -1323,7 +1323,7 @@
                     "status": "FAIL"
                 },
                 {
-                    "message": "assert_throws: function \"function () { throw e }\" threw object \"OperationError: Failed to set local answer sdp: Failed to push down transport description for video: Local fingerprint does not match identity. Expected: sha-256 29:15:1D:A0:AE:C9:78:CA:5A:1D:54:B6:98:1D:A0:6D:B1:DB:AC:88:0F:F5:7B:FA:D0:05:84:E8:9D:9F:C8:7A Got: sha-256 93:DD:EA:CC:7E:3B:B6:17:D8:A7:31:58:24:ED:E9:6C:3B:24:E0:B2:94:89:6B:B7:D6:EB:01:4A:81:9D:6F:EB\" that is not a DOMException InvalidModificationError: property \"code\" is equal to 0, expected 13",
+                    "message": "assert_throws: function \"function () { throw e }\" threw object \"OperationError: Failed to set local answer sdp: Failed to push down transport description for video: Local fingerprint does not match identity. Expected: sha-256 C3:A5:F6:B0:43:DF:B0:E4:A4:78:98:06:D6:A4:FE:CB:9D:9A:74:6A:E2:7F:B1:B8:D8:5C:90:E5:3D:C9:DA:29 Got: sha-256 92:1E:35:C6:B8:E1:F1:6D:C5:67:D2:96:8B:45:5C:2F:1F:06:57:C1:AB:8D:0E:1D:02:77:5D:FE:26:7F:25:41\" that is not a DOMException InvalidModificationError: property \"code\" is equal to 0, expected 13",
                     "name": "setLocalDescription() with answer not created by own createAnswer() should reject with InvalidModificationError",
                     "status": "FAIL"
                 },
@@ -2338,12 +2338,12 @@
                     "status": "FAIL"
                 },
                 {
-                    "message": "promise_test: Unhandled rejection with value: object \"NotSupportedError: Only secure origins are allowed (see: https://goo.gl/Y0ZkNV).\"",
+                    "message": "promise_test: Unhandled rejection with value: object \"TypeError: pc.getTransceivers is not a function\"",
                     "name": "offerToReceiveAudio option should be ignored if a non-stopped \"sendrecv\" transceiver exists",
                     "status": "FAIL"
                 },
                 {
-                    "message": "promise_test: Unhandled rejection with value: object \"NotSupportedError: Only secure origins are allowed (see: https://goo.gl/Y0ZkNV).\"",
+                    "message": "promise_test: Unhandled rejection with value: object \"TypeError: pc.getTransceivers is not a function\"",
                     "name": "offerToReceiveAudio set to false with a track should create a \"sendonly\" transceiver",
                     "status": "FAIL"
                 },
@@ -2353,7 +2353,7 @@
                     "status": "FAIL"
                 },
                 {
-                    "message": "promise_test: Unhandled rejection with value: object \"NotSupportedError: Only secure origins are allowed (see: https://goo.gl/Y0ZkNV).\"",
+                    "message": "promise_test: Unhandled rejection with value: object \"TypeError: pc.getTransceivers is not a function\"",
                     "name": "subsequent offerToReceiveAudio set to false with a track should change the direction to \"sendonly\"",
                     "status": "FAIL"
                 },
@@ -2373,12 +2373,12 @@
                     "status": "FAIL"
                 },
                 {
-                    "message": "promise_test: Unhandled rejection with value: object \"NotSupportedError: Only secure origins are allowed (see: https://goo.gl/Y0ZkNV).\"",
+                    "message": "promise_test: Unhandled rejection with value: object \"TypeError: pc.getTransceivers is not a function\"",
                     "name": "offerToReceiveVideo option should be ignored if a non-stopped \"sendrecv\" transceiver exists",
                     "status": "FAIL"
                 },
                 {
-                    "message": "promise_test: Unhandled rejection with value: object \"NotSupportedError: Only secure origins are allowed (see: https://goo.gl/Y0ZkNV).\"",
+                    "message": "promise_test: Unhandled rejection with value: object \"TypeError: pc.getTransceivers is not a function\"",
                     "name": "offerToReceiveVideo set to false with a track should create a \"sendonly\" transceiver",
                     "status": "FAIL"
                 },
@@ -2388,7 +2388,7 @@
                     "status": "FAIL"
                 },
                 {
-                    "message": "promise_test: Unhandled rejection with value: object \"NotSupportedError: Only secure origins are allowed (see: https://goo.gl/Y0ZkNV).\"",
+                    "message": "promise_test: Unhandled rejection with value: object \"TypeError: pc.getTransceivers is not a function\"",
                     "name": "subsequent offerToReceiveVideo set to false with a track should change the direction to \"sendonly\"",
                     "status": "FAIL"
                 },
@@ -3619,7 +3619,7 @@
                     "status": "FAIL"
                 },
                 {
-                    "message": "assert_throws: function \"function () { throw e }\" threw object \"OperationError: Failed to set local offer sdp: Failed to push down transport description for data: Local fingerprint does not match identity. Expected: sha-256 5A:4E:B9:F5:FD:7F:07:0D:AD:F8:D4:14:0F:8A:4E:A7:48:45:B8:77:C1:2F:1C:5C:26:1F:0D:1D:09:2E:E8:71 Got: sha-256 A3:16:3C:C6:58:4E:AF:14:B0:C9:A6:8A:4F:57:6E:F7:EB:DD:DC:B3:46:05:63:A9:60:2D:45:9D:90:C3:F9:67\" that is not a DOMException InvalidModificationError: property \"code\" is equal to 0, expected 13",
+                    "message": "assert_throws: function \"function () { throw e }\" threw object \"OperationError: Failed to set local offer sdp: Failed to push down transport description for data: Local fingerprint does not match identity. Expected: sha-256 53:5B:48:87:D8:F0:2E:21:7F:B5:F8:F6:03:3E:3F:53:F7:06:18:3A:C2:07:C9:64:33:20:87:C1:29:6B:60:83 Got: sha-256 4E:6B:EF:C5:5F:9D:61:E3:CD:19:3C:84:ED:7F:8E:12:D5:43:E0:83:D7:74:B0:E6:00:D5:47:B1:88:CA:F1:43\" that is not a DOMException InvalidModificationError: property \"code\" is equal to 0, expected 13",
                     "name": "setLocalDescription() with offer not created by own createOffer() should reject with InvalidModificationError",
                     "status": "FAIL"
                 },
Comparison of Firefox reports
diff --git a/wptreport-fx-master.json b/wptreport-fx-patch.json
index 63f8e9d..0ce2193 100644
--- a/wptreport-fx-master.json
+++ b/wptreport-fx-patch.json
@@ -92,8 +92,39 @@
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": null,
+                    "name": "replaceTrack() sets the track attribute to a new track.",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "replaceTrack() sets the track attribute to null.",
+                    "status": "PASS"
+                },
+                {
+                    "message": "assert_equals: expected object \"[object AudioStreamTrack]\" but got object \"[object AudioStreamTrack]\"",
+                    "name": "replaceTrack() does not set the track synchronously.",
+                    "status": "FAIL"
+                },
+                {
+                    "message": null,
+                    "name": "replaceTrack() rejects when the peer connection is closed.",
+                    "status": "PASS"
+                },
+                {
+                    "message": "assert_unreached: Expected replaceTrack() to be rejected with InvalidModificationError but the promise was resolved. Reached unreachable code",
+                    "name": "replaceTrack() rejects when invoked after removeTrack().",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_unreached: Expected replaceTrack() to be rejected with InvalidModificationError but the promise was resolved. Reached unreachable code",
+                    "name": "replaceTrack() rejects after a subsequent removeTrack().",
+                    "status": "FAIL"
+                }
+            ],
             "test": "/webrtc/RTCPeerConnection-setRemoteDescription-replaceTrack.https.html"
         },
         {
@@ -115,8 +146,34 @@
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": null,
+                    "name": "setRemoteDescription should trigger ontrack event when the MSID of the stream is is parsed.",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "setRemoteDescription() with m= line of recvonly direction should not trigger track event",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "addTrack() should cause remote connection to fire ontrack when setRemoteDescription()",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "addTransceiver('video') should cause remote connection to fire ontrack when setRemoteDescription()",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "addTransceiver() with inactive direction should not cause remote connection to fire ontrack when setRemoteDescription()",
+                    "status": "PASS"
+                }
+            ],
             "test": "/webrtc/RTCPeerConnection-ontrack.https.html"
         },
         {
@@ -168,8 +225,14 @@
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": null,
+                    "name": "insertDTMF with duration greater than 6000 should be clamped to 6000",
+                    "status": "PASS"
+                }
+            ],
             "test": "/webrtc/RTCDTMFSender-ontonechange-long.https.html"
         },
         {
@@ -429,8 +492,74 @@
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": null,
+                    "name": "insertDTMF() with default duration and intertoneGap should fire tonechange events at the expected time",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "insertDTMF() with explicit duration and intertoneGap should fire tonechange events at the expected time",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "insertDTMF('') should not fire any tonechange event, including for '' tone",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "insertDTMF() with duration less than 40 should be clamped to 40",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "insertDTMF() with interToneGap less than 30 should be clamped to 30",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "insertDTMF with comma should delay next tonechange event for a constant 2000ms",
+                    "status": "PASS"
+                },
+                {
+                    "message": "assert_greater_than: More tonechange event is fired than expected expected a number greater than 0 but got 0",
+                    "name": "insertDTMF() with transceiver stopped in the middle should stop future tonechange events from firing",
+                    "status": "FAIL"
+                },
+                {
+                    "message": null,
+                    "name": "Calling insertDTMF() in the middle of tonechange events should cause future tonechanges to be updated to new tones",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "Calling insertDTMF() multiple times in the middle of tonechange events should cause future tonechanges to be updated the last provided tones",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "Calling insertDTMF('') in the middle of tonechange events should stop future tonechange events from firing",
+                    "status": "PASS"
+                },
+                {
+                    "message": "RTCRtpSender has no track",
+                    "name": "Setting transceiver.currentDirection to recvonly in the middle of tonechange events should stop future tonechange events from firing",
+                    "status": "FAIL"
+                },
+                {
+                    "message": null,
+                    "name": "Tone change event constructor works",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "Tone change event with unexpected name should not crash",
+                    "status": "PASS"
+                }
+            ],
             "test": "/webrtc/RTCDTMFSender-ontonechange.https.html"
         },
         {
@@ -457,8 +586,19 @@
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": "assert_true: Expect statsReport to contain stats object of type outbound-rtp expected true got false",
+                    "name": "sender.getStats() via addTransceiver should return stats report containing outbound-rtp stats",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_true: Expect dictionary.ssrc to be unsigned integer expected true got false",
+                    "name": "sender.getStats() via addTrack should return stats report containing outbound-rtp stats",
+                    "status": "FAIL"
+                }
+            ],
             "test": "/webrtc/RTCRtpSender-getStats.https.html"
         },
         {
@@ -469,8 +609,19 @@
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": "assert_true: Expect statsReport to contain stats object of type inbound-rtp expected true got false",
+                    "name": "receiver.getStats() via addTransceiver should return stats report containing inbound-rtp stats",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_true: Expect dictionary.ssrc to be unsigned integer expected true got false",
+                    "name": "receiver.getStats() via addTrack should return stats report containing inbound-rtp stats",
+                    "status": "FAIL"
+                }
+            ],
             "test": "/webrtc/RTCRtpReceiver-getStats.https.html"
         },
         {
@@ -813,8 +964,14 @@
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": "assert_greater_than: Expect CSRCs to be available after RTP connection is established expected a number greater than 0 but got 0",
+                    "name": "getContributingSources() should return list of CSRC after connection is established",
+                    "status": "FAIL"
+                }
+            ],
             "test": "/webrtc/RTCRtpReceiver-getContributingSources.https.html"
         },
         {
@@ -961,8 +1118,44 @@
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": null,
+                    "name": "insertDTMF() should succeed if tones contains valid DTMF characters",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "insertDTMF() should throw InvalidCharacterError if tones contains invalid DTMF characters",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "insertDTMF() should throw InvalidStateError if transceiver is stopped",
+                    "status": "PASS"
+                },
+                {
+                    "message": "assert_equals: expected (string) \"inactive\" but got (object) null",
+                    "name": "insertDTMF() should throw InvalidStateError if transceiver.currentDirection is recvonly",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_equals: expected (string) \"inactive\" but got (object) null",
+                    "name": "insertDTMF() should throw InvalidStateError if transceiver.currentDirection is inactive",
+                    "status": "FAIL"
+                },
+                {
+                    "message": null,
+                    "name": "insertDTMF() should set toneBuffer to provided tones normalized, with old tones overridden",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "insertDTMF() after remove and close should reject",
+                    "status": "PASS"
+                }
+            ],
             "test": "/webrtc/RTCDTMFSender-insertDTMF.https.html"
         },
         {
@@ -1086,8 +1279,109 @@
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": null,
+                    "name": "Main test driver",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "Test driver for asyncInitCertificate",
+                    "status": "PASS"
+                },
+                {
+                    "message": "assert_unreached: Failed to run asyncInitTransports: ReferenceError: RTCSctpTransport is not defined Reached unreachable code",
+                    "name": "Test driver for asyncInitTransports",
+                    "status": "FAIL"
+                },
+                {
+                    "message": null,
+                    "name": "Test driver for asyncInitMediaStreamTrack",
+                    "status": "PASS"
+                },
+                {
+                    "message": "assert_throws: interface object didn't throw TypeError when called as a constructor function \"function bound () {\n    [native code]\n}\" did not throw",
+                    "name": "EventTarget interface: existence and properties of interface object",
+                    "status": "FAIL"
+                },
+                {
+                    "message": null,
+                    "name": "EventTarget interface object length",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "EventTarget interface object name",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "EventTarget interface: existence and properties of interface prototype object",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "EventTarget interface: existence and properties of interface prototype object's \"constructor\" property",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "EventTarget interface: existence and properties of interface prototype object's @@unscopables property",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "MediaStreamTrack interface: existence and properties of interface object",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "MediaStreamTrack interface object length",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "MediaStreamTrack interface object name",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "MediaStreamTrack interface: existence and properties of interface prototype object",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "MediaStreamTrack interface: existence and properties of interface prototype object's \"constructor\" property",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "MediaStreamTrack interface: existence and properties of interface prototype object's @@unscopables property",
+                    "status": "PASS"
+                },
+                {
+                    "message": "assert_equals: idlTestObjects.mediaStreamTrack's prototype is not MediaStreamTrack.prototype expected object \"[object MediaStreamTrackPrototype]\" but got object \"[object AudioStreamTrackPrototype]\"",
+                    "name": "MediaStreamTrack must be primary interface of idlTestObjects.mediaStreamTrack",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_equals: class string of idlTestObjects.mediaStreamTrack expected \"[object MediaStreamTrack]\" but got \"[object AudioStreamTrack]\"",
+                    "name": "Stringification of idlTestObjects.mediaStreamTrack",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_equals: generateMediaStreamTrack('audio')'s prototype is not MediaStreamTrack.prototype expected object \"[object MediaStreamTrackPrototype]\" but got object \"[object AudioStreamTrackPrototype]\"",
+                    "name": "MediaStreamTrack must be primary interface of generateMediaStreamTrack('audio')",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_equals: class string of generateMediaStreamTrack('audio') expected \"[object MediaStreamTrack]\" but got \"[object AudioStreamTrack]\"",
+                    "name": "Stringification of generateMediaStreamTrack('audio')",
+                    "status": "FAIL"
+                }
+            ],
             "test": "/webrtc/interfaces.https.html"
         },
         {
@@ -1876,8 +2170,84 @@
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": null,
+                    "name": "createOffer() with offerToReceiveAudio set to false should not create a transceiver",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "createOffer() with offerToReceiveAudio should create a \"recvonly\" transceiver",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "offerToReceiveAudio option should be ignored if a non-stopped \"recvonly\" transceiver exists",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "offerToReceiveAudio option should be ignored if a non-stopped \"sendrecv\" transceiver exists",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "offerToReceiveAudio set to false with a track should create a \"sendonly\" transceiver",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "offerToReceiveAudio set to false with a \"recvonly\" transceiver should change the direction to \"inactive\"",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "subsequent offerToReceiveAudio set to false with a track should change the direction to \"sendonly\"",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "createOffer() with offerToReceiveVideo set to false should not create a transceiver",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "createOffer() with offerToReceiveVideo should create a \"recvonly\" transceiver",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "offerToReceiveVideo option should be ignored if a non-stopped \"recvonly\" transceiver exists",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "offerToReceiveVideo option should be ignored if a non-stopped \"sendrecv\" transceiver exists",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "offerToReceiveVideo set to false with a track should create a \"sendonly\" transceiver",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "offerToReceiveVideo set to false with a \"recvonly\" transceiver should change the direction to \"inactive\"",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "subsequent offerToReceiveVideo set to false with a track should change the direction to \"sendonly\"",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "offerToReceiveAudio and Video should create two \"recvonly\" transceivers",
+                    "status": "PASS"
+                }
+            ],
             "test": "/webrtc/RTCPeerConnection-createOffer-offerToReceive.html"
         },
         {
@@ -2136,9 +2506,9 @@
             "status": "OK",
             "subtests": [
                 {
-                    "message": "Test timed out",
+                    "message": null,
                     "name": "Can set up a basic WebRTC call.",
-                    "status": "TIMEOUT"
+                    "status": "PASS"
                 }
             ],
             "test": "/webrtc/simplecall.https.html"
@@ -2524,8 +2894,49 @@
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": null,
+                    "name": "addTrack when pc is closed should throw InvalidStateError",
+                    "status": "PASS"
+                },
+                {
+                    "message": "promise_test: Unhandled rejection with value: object \"TypeError: Not enough arguments to RTCPeerConnection.addTrack.\"",
+                    "name": "addTrack with single track argument and no mediaStream should succeed",
+                    "status": "FAIL"
+                },
+                {
+                    "message": null,
+                    "name": "addTrack with single track argument and single mediaStream should succeed",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "addTrack with single track argument and multiple mediaStreams should succeed",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "Adding the same track multiple times should throw InvalidAccessError",
+                    "status": "PASS"
+                },
+                {
+                    "message": "Not enough arguments to RTCPeerConnection.addTrack.",
+                    "name": "addTrack with existing sender with null track, same kind, and recvonly direction should reuse sender",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "Not enough arguments to RTCPeerConnection.addTrack.",
+                    "name": "addTrack with existing sender with null track, same kind, and sendrecv direction should create new sender",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "Not enough arguments to RTCPeerConnection.addTrack.",
+                    "name": "addTrack with existing sender with null track, different kind, and recvonly direction should create new sender",
+                    "status": "FAIL"
+                }
+            ],
             "test": "/webrtc/RTCPeerConnection-addTrack.https.html"
         },
         {
@@ -2576,14 +2987,141 @@
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": null,
+                    "name": "getStats() with no argument should succeed",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "getStats(null) should succeed",
+                    "status": "PASS"
+                },
+                {
+                    "message": "assert_unreached: Should have rejected: undefined Reached unreachable code",
+                    "name": "getStats() with track not added to connection should reject with InvalidAccessError",
+                    "status": "FAIL"
+                },
+                {
+                    "message": null,
+                    "name": "getStats() with track added via addTrack should succeed",
+                    "status": "PASS"
+                },
+                {
+                    "message": "Invalid media kind",
+                    "name": "getStats() with track added via addTransceiver should succeed",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_unreached: Should have rejected: undefined Reached unreachable code",
+                    "name": "getStats() with track associated with more than one sender should reject with InvalidAccessError",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_unreached: Should have rejected: undefined Reached unreachable code",
+                    "name": "getStats() with track associated with both sender and receiver should reject with InvalidAccessError",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_true: Expect statsReport to contain stats object of type peer-connection expected true got false",
+                    "name": "getStats() with no argument should return stats report containing peer-connection stats on an empty PC",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_true: Expect dictionary.ssrc to be unsigned integer expected true got false",
+                    "name": "getStats() with no argument should return stats report containing peer-connection stats and outbound-track-stats",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "promise_test: Unhandled rejection with value: object \"TypeError: Not enough arguments to RTCPeerConnection.addTrack.\"",
+                    "name": "getStats() with no argument should return stats for no-stream tracks",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_true: Expect dictionary.ssrc to be unsigned integer expected true got false",
+                    "name": "getStats() on track associated with RtpSender should return stats report containing outbound-rtp stats",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_true: Expect statsReport to contain stats object of type inbound-rtp expected true got false",
+                    "name": "getStats() on track associated with RtpReceiver should return stats report containing inbound-rtp stats",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_unreached: test failed with error: Error: assert_true: Expect dictionary.ssrc to be unsigned integer expected true got false Reached unreachable code",
+                    "name": "getStats() with connected peer connections having tracks and data channel should return all mandatory to implement stats",
+                    "status": "FAIL"
+                }
+            ],
             "test": "/webrtc/RTCPeerConnection-getStats.https.html"
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": null,
+                    "name": "addTransceiver - Calling removeTrack when connection is closed should throw InvalidStateError",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "addTrack - Calling removeTrack when connection is closed should throw InvalidStateError",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "addTransceiver - Calling removeTrack on different connection that is closed should throw InvalidStateError",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "addTrack - Calling removeTrack on different connection that is closed should throw InvalidStateError",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "addTransceiver - Calling removeTrack on different connection should throw InvalidAccessError",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "addTrack - Calling removeTrack on different connection should throw InvalidAccessError",
+                    "status": "PASS"
+                },
+                {
+                    "message": "assert_equals: direction should not be altered expected \"sendrecv\" but got \"recvonly\"",
+                    "name": "addTransceiver - Calling removeTrack with valid sender should set sender.track to null",
+                    "status": "FAIL"
+                },
+                {
+                    "message": null,
+                    "name": "addTrack - Calling removeTrack with valid sender should set sender.track to null",
+                    "status": "PASS"
+                },
+                {
+                    "message": "assert_equals: expected \"sendrecv\" but got \"sendonly\"",
+                    "name": "Calling removeTrack with currentDirection sendrecv should set direction to recvonly",
+                    "status": "FAIL"
+                },
+                {
+                    "message": null,
+                    "name": "Calling removeTrack with currentDirection sendonly should set direction to inactive",
+                    "status": "PASS"
+                },
+                {
+                    "message": "assert_equals: expected \"recvonly\" but got \"inactive\"",
+                    "name": "Calling removeTrack with currentDirection recvonly should not change direction",
+                    "status": "FAIL"
+                },
+                {
+                    "message": null,
+                    "name": "Calling removeTrack with currentDirection inactive should not change direction",
+                    "status": "PASS"
+                }
+            ],
             "test": "/webrtc/RTCPeerConnection-removeTrack.https.html"
         },
         {
@@ -2738,14 +3276,111 @@
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": "Not enough arguments to RTCPeerConnection.addTrack.",
+                    "name": "addTrack() without setLocalDescription() yields track stats",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_true: Has stats for stream expected true got false",
+                    "name": "addTrack() without setLocalDescription() yields media stream stats",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "Not enough arguments to RTCPeerConnection.addTrack.",
+                    "name": "addTrack() with setLocalDescription() yields track stats",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_true: Has stats for stream expected true got false",
+                    "name": "addTrack() with setLocalDescription() yields media stream stats",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_true: Has stats for track and stream expected true got false",
+                    "name": "addTrack(): Media stream stats references track stats",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "assert_true: Has stats for track and stream expected true got false",
+                    "name": "Legacy addStream(): Media stream stats references track stats",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "Not enough arguments to RTCPeerConnection.addTrack.",
+                    "name": "O/A exchange yields outbound RTP stream stats for sending track",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "Not enough arguments to RTCPeerConnection.addTrack.",
+                    "name": "O/A exchange yields inbound RTP stream stats for receiving track",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "Not enough arguments to RTCPeerConnection.addTrack.",
+                    "name": "replaceTrack() before offer: new track attachment stats present",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "Not enough arguments to RTCPeerConnection.addTrack.",
+                    "name": "replaceTrack() after offer, before answer: new track attachment stats present",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "Not enough arguments to RTCPeerConnection.addTrack.",
+                    "name": "replaceTrack() after answer: new track attachment stats present",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "Not enough arguments to RTCPeerConnection.addTrack.",
+                    "name": "replaceTrack(): original track attachment stats present after replacing",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "promise_test: Unhandled rejection with value: object \"TypeError: outboundTrackStats is null\"",
+                    "name": "RTCRtpSender.getStats() contains only outbound-rtp and related stats",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "promise_test: Unhandled rejection with value: object \"TypeError: outboundTrackStats is null\"",
+                    "name": "RTCRtpReceiver.getStats() contains only inbound-rtp and related stats",
+                    "status": "FAIL"
+                },
+                {
+                    "message": null,
+                    "name": "RTCPeerConnection.getStats(sendingTrack) is the same as RTCRtpSender.getStats()",
+                    "status": "PASS"
+                },
+                {
+                    "message": null,
+                    "name": "RTCPeerConnection.getStats(receivingTrack) is the same as RTCRtpReceiver.getStats()",
+                    "status": "PASS"
+                },
+                {
+                    "message": "assert_unreached: Should have rejected: undefined Reached unreachable code",
+                    "name": "RTCPeerConnection.getStats(track) throws InvalidAccessError when there are zero senders or receivers for the track",
+                    "status": "FAIL"
+                },
+                {
+                    "message": "promise_test: Unhandled rejection with value: object \"TypeError: Not enough arguments to RTCPeerConnection.addTrack.\"",
+                    "name": "RTCPeerConnection.getStats(track) throws InvalidAccessError when there are multiple senders for the track",
+                    "status": "FAIL"
+                }
+            ],
             "test": "/webrtc/RTCPeerConnection-track-stats.https.html"
         },
         {
             "message": null,
-            "status": "TIMEOUT",
-            "subtests": [],
+            "status": "OK",
+            "subtests": [
+                {
+                    "message": "assert_greater_than: Expect SSRCs to be available after RTP connection is established expected a number greater than 0 but got 0",
+                    "name": "getContributingSources() should return list of CSRC after connection is established",
+                    "status": "FAIL"
+                }
+            ],
             "test": "/webrtc/RTCRtpReceiver-getSynchronizationSources.https.html"
         },
         {

@foolip
Copy link
Member

foolip commented May 9, 2018

Pinging all reviewers!

@jugglinmike
Copy link
Contributor Author

@youennf Would you mind taking a look?

@alvestrand
Copy link
Contributor

the Chrome failures because of "NotSupported" indicate that the tests are being run in a wrong environment. Since these are .https.html tests, they should be running in a secure environment (https), and therefore the objects should be supported.
I suggest fixing the environment problem first, since this will give misleading results for any .https.html test.
Apart from that - seems like a Good Thing to me!

@fippo
Copy link
Contributor

fippo commented May 28, 2018

Given that we are still cleaning up after the last attempt to avoid getUserMedia repeating that pattern seems like a bad idea. Also Edge does not support captureStream.

I would not be surprised if the flakyness comes from having too many streams open due to lack of cleaning up.

@foolip
Copy link
Member

foolip commented May 28, 2018

Given that we are still cleaning up after the last attempt to avoid getUserMedia repeating that pattern seems like a bad idea.

What was the approach that time, and why didn't it work?

Also Edge does not support captureStream.

Any WebDriver solution we come up with would initially not be supported by any browser at all, so that canvas.captureStream is already supported on Chrome, Firefox and Safari is a big plus.

As for what I think: RTCPeerConnection may well be testable with captureStream, but getUserMedia itself is not. https://w3c.github.io/mediacapture-main/ is pretty complicated and to test something like https://w3c.github.io/mediacapture-main/#dfn-selectsettings it's necessary to mock the existence of devices.

@fippo
Copy link
Contributor

fippo commented May 28, 2018

What was the approach that time, and why didn't it work?

using addTransceiver. The recording of https://www.w3.org/2018/04/26-webrtc-minutes has some details, the tl;dr is that nobody implemented addTransceiver at that point (and it yields a track which is not active in any way)

@foolip
Copy link
Member

foolip commented May 28, 2018

Not implemented by anyone and already being implemented by 3 engines is quite an important difference in these approaches. Unless there is something fundamental/special about getUserMedia that nothing other than getUserMedia would get us?

@jugglinmike
Copy link
Contributor Author

@alvestrand that seems to be a mistake in the way the test was authored, not an environment error. The diff doesn't have enough context to show this, but the 6 sub-test failures in Chromium related to secure origins all come from /webrtc/RTCPeerConnection-createOffer-offerToReceive.html (results on wpt.fyi) which is not executed via HTTPS because it is not named according to the convention.

The fix for that would be to simply re-name the test. However, that change isn't necessary with this patch applied. Renaming the file would actually be undesirable if we merged this patch because requiring HTTPS would make the test more specific than necessary.

@jan-ivar
Copy link
Member

jan-ivar commented Jun 4, 2018

@jugglinmike That raises a good point. AFAIK nothing in the WebRTC spec says RTCPeerConnection may be limited to HTTPS only, so we need to test it in HTTP as well. canvas.captureStream therefore seems like a better fit here than gUM, since it works in http, at least when I test Chrome and Firefox.

@foolip
Copy link
Member

foolip commented Jun 4, 2018

@jugglinmike That raises a good point. AFAIK nothing in the WebRTC spec says RTCPeerConnection may be limited to HTTPS only, so we need to test it in HTTP as well

@alvestrand is this an oversight? Does no spec describe how RTCPeerConnection is limited to secure contexts in Chromium?

@dontcallmedom
Copy link
Contributor

see also w3ctag/design-reviews#228

@jan-ivar
Copy link
Member

jan-ivar commented Jun 4, 2018

Does no spec describe how RTCPeerConnection is limited to secure contexts in Chromium?

It isn't, as that fiddle shows both captureStream and RTCPeerConnection wfm in Chrome and Canary.

@foolip
Copy link
Member

foolip commented Jun 4, 2018

Ah, I guess it's just getUserMedia() itself.

@jan-ivar
Copy link
Member

jan-ivar commented Jun 4, 2018

Not implemented by anyone ...

FWIW, Firefox does implement transceivers now, so using them for fake tracks works there.

@jan-ivar
Copy link
Member

jan-ivar commented Jun 4, 2018

Ah, I guess it's just getUserMedia() itself.

Yes, though even there, Mediacapture allows limited (non-persistent) access to getUserMedia in http, so Firefox (and last I tried, Edge) are compliant. We are considering removing support in http however.

@jugglinmike
Copy link
Contributor Author

We've had a month to discuss this patch, and we haven't identified any reason not to move forward with it. Because I authored the changeset, I'd prefer if one of my reviewers merged on my behalf. @jan-ivar think it's ready?

@fippo
Copy link
Contributor

fippo commented Jun 14, 2018

the test failure might be due to https://bugs.chromium.org/p/chromium/issues/detail?id=848747 -- @henbos can you take a look if the unstable version used includes your fix?

@henbos
Copy link
Contributor

henbos commented Jun 15, 2018

the test failure might be due to https://bugs.chromium.org/p/chromium/issues/detail?id=848747 -- @henbos can you take a look if the unstable version used includes your fix?

Not sure how to check, can we fix the conflict and rerun the test? Fingers crossed

@jugglinmike
Copy link
Contributor Author

No problem. I've resolved the new conflicts introduced by today's earlier commit to master.

@fippo
Copy link
Contributor

fippo commented Jun 15, 2018

@henbos 69.0.3452.0-1 -- you probably know omahaproxy better than I do to find out if this simply doesn't have the fix for the issue yet.

Still flaky in

/webrtc/RTCPeerConnection-setRemoteDescription.html` |
 `Negotiation should fire signalingsstate events`

I'd say merge and if this is still an issue its on the two of us, not @jugglinmike

@foolip
Copy link
Member

foolip commented Jun 19, 2018

@eric-carlson @youennf you indicated yesterday that this won't actually work in Safari. Can you elaborate?

@eric-carlson
Copy link

@eric-carlson @youennf you indicated yesterday that this won't actually work in Safari. Can you elaborate?

@foolip MediaStreamAudioDestinationNode doesn't work in Safari yet.

Copy link
Contributor

@soareschen soareschen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC as discussed in the F2F Stockholm meeting, getNoiseStream() need to feature detect on the captureStream() and MediaStreamAudioDestinationNode. If the functions are not available on the running browser, getNoiseStream() should revert back to using getUserMedia() to generate the stream.

@jugglinmike
Copy link
Contributor Author

Sounds right to me, @soareschen! I've resolved the new conflicts, updated the new tests, and implemented the feature detection. Would you mind merging this if it looks good to you?

Copy link
Contributor

@fippo fippo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ship it. i'll deal with the consequences for #11694

Copy link
Contributor

@alvestrand alvestrand left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is good enough. Not enthusiastic about the name, but that's a bikeshed. Let's stop using devices when we can.

var tracks = [];

if (!HTMLCanvasElement.prototype.captureStream ||
typeof MediaStreamAudioDestinationNode === 'undefined') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this condition, the noise streams wouldn't be used if either of the feature is missing. Would it be better if the condition be checked depending on individual capability, so that tests that request only one of the supported capabilities still get to bypass getUserMedia?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, at the cost of some complexity in the helper. I've done this somewhat verbosely to help readers understand what is going on.

Copy link
Member

@jan-ivar jan-ivar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, looks like I had a pending review sitting around that github remembered for me. FYI

// @param {boolean} [caps.video] - flag indicating whether the generated stream
// should include a video track
function getNoiseStream(caps) {
var tracks = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe const would be more precise

// should include an audio track
// @param {boolean} [caps.video] - flag indicating whether the generated stream
// should include a video track
function getNoiseStream(caps) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we could use async function here, and use default args.

async function getNoiseStream(caps = {}) {

for benefits shown below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

function getNoiseStream(caps) {
var tracks = [];

if (caps && caps.audio) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...then we wouldn't need to test caps here.

tracks.push(trackFactories.video());
}

return Promise.resolve(new MediaStream(tracks));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...and we could just

return new MediaStream(tracks);


return Promise.resolve(new MediaStream(tracks));
}

// Obtain a MediaStreamTrack of kind using getUserMedia.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should update comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely

@jugglinmike
Copy link
Contributor Author

Thanks, @soareschen!

@jan-ivar it looks like you're the final stakeholder we need to approve this. If it looks good to you, would you mind merging it?

Copy link
Member

@jan-ivar jan-ivar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm. Many thanks!

I don't have push rights here, so someone else would need to merge it.

@jugglinmike
Copy link
Contributor Author

Sure thing, @jan-ivar. @soareschen would you do the honors?

@soareschen soareschen merged commit eee513c into web-platform-tests:master Jul 7, 2018
@youennf
Copy link
Contributor

youennf commented Jul 7, 2018

IIRC as discussed in the F2F Stockholm meeting, getNoiseStream() need to feature detect on the captureStream() and MediaStreamAudioDestinationNode. If the functions are not available on the running browser, getNoiseStream() should revert back to using getUserMedia() to generate the stream.

This is not exactly correct, although it is true that it was not discussed deeply.
I think WebAudio/CaptureStream should be used when:

  • There is no possibility to use getUserMedia
  • Or when the content (pixel or audio sample) is actually checked on the other side when transmitted (which does not seem to be the case for more of the modified tests).

Some differences between the two approaches:

  • CaptureStream will usually create RGBA video frames while getUserMedia will usually create YUV frames. This generation will usually happen in main frame while capture typically happen in a background thread
  • Creating an AudioContext is changing the behavior of the audio session which may have some side effects. Good to test for sure but it would be best to test it specifically.
    Being closer to what web sites do is better in my opinion.
    It is also cheaper to implement a mock getUserMedia that all browser probably have than requiring to support captureStream/WebAudio+PC.

Btw, the current way of testing whether getUserMedia should be used or not will not work for WebKit. window.MediaStreamAudioDestinationNode for instance is defined but cannot currently be used with RTCPeerConnection.

Ideally, there should be an easy way to decide whether to switch between the two approaches.
Maybe the switch could be made specific as part of testharnessreport.js.
It could be also part of testdriver-vendor.js which might make sense also if we think that captureStream/WebAudio would be used only when WebDriver support of getUserMedia is not available.

@jan-ivar
Copy link
Member

jan-ivar commented Jul 9, 2018

Being closer to what web sites do is better in my opinion.

Makes sense once we have WebDriver-controlled getUserMedia.

I think ideally we'd like to test all the things—especially web-exposed features—so we have a bit of a false choice atm. RTCPeerConnection is source-agnostic, even though camera and microphone are obviously the most important. Firefox's regular {fake: true} mock devices sadly don't reuse much of the camera and microphone stacks—though they do run in the background—so it's a wash for us.

Or when the content (pixel or audio sample) is actually checked on the other side when transmitted (which does not seem to be the case for more of the modified tests).

I think in those cases, captureStream and webaudio can make sense, as they naturally give tests better control over the output. Mocks in contrast would be limited to fixed output, which might suffice, if we can get vendors to agree on what the output should be. Happy to do our part there.

Short-term, is there an opportunity here to have getNoiseStream() use captureStream and webaudio to mimic the video output and sample rates of Safari's mock devices on other systems? Then we might be able to write such tests already.

window.MediaStreamAudioDestinationNode for instance is defined but cannot currently be used with RTCPeerConnection.

Is there a way to feature-detect that they don't work with RTCPeerConnection?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.