UNPKG

webrtc-adapter-test

Version:

Hide browser differences in WebRTC APIs (test package name)

1,598 lines (1,487 loc) 65.9 kB
/* * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. */ /* jshint node: true */ /* global Promise */ 'use strict'; // This is a basic test file for use with testling and webdriver. // The test script language comes from tape. var test = require('tape'); var webdriver = require('selenium-webdriver'); var seleniumHelpers = require('./selenium-lib'); // Start of tests. // Due to loading adapter.js as a module, there is no need to use webdriver for // this test (note that this uses Node.js's require import function). test('Log suppression', function(t) { // Define test var logCount = 0; var saveConsole = console.log.bind(console); console.log = function() { logCount++; saveConsole.apply(saveConsole, arguments); }; var m = require('../adapter.js'); m.webrtcUtils.log('test'); console.log = saveConsole; // Run test. t.ok(typeof m.webrtcDetectedBrowser !== 'undefined', 'adapter.js loaded ' + 'as a module'); t.ok(logCount === 0, 'adapter.js does not use console.log'); t.end(); }); // Fiddle with the UA string to test the extraction does not throw errors. // No need for webdriver in this test. test('Browser version extraction helper', function(t) { var m = require('../adapter.js'); // Chrome and Chromium. var ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like ' + 'Gecko) Chrome/45.0.2454.101 Safari/537.36'; t.equal(m.webrtcUtils.extractVersion(ua, /Chrom(e|ium)\/([0-9]+)\./, 2), 45, 'version extraction'); ua = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like ' + 'Gecko) Ubuntu Chromium/45.0.2454.85 Chrome/45.0.2454.85 Safari/537.36'; t.equal(m.webrtcUtils.extractVersion(ua, /Chrom(e|ium)\/([0-9]+)\./, 2), 45, 'version extraction'); // Various UA strings from device simulator, not matching. ua = 'Mozilla/5.0 (Linux; Android 4.3; Nexus 10 Build/JSS15Q) ' + 'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2307.2 Safari/537.36'; t.equal(m.webrtcUtils.extractVersion(ua, /Chrom(e|ium)\/([0-9]+)\./, 2), 42, 'version extraction'); ua = 'Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) ' + 'AppleWebKit/600.1.3 (KHTML, like Gecko) Version/8.0 Mobile/12A4345d ' + 'Safari/600.1.4'; t.equal(m.webrtcUtils.extractVersion(ua, /Chrom(e|ium)\/([0-9]+)\./, 2), null, 'version extraction'); ua = 'Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19' + '(KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true'; t.equal(m.webrtcUtils.extractVersion(ua, /Chrom(e|ium)\/([0-9]+)\./, 2), null, 'version extraction'); // Opera, should match chrome/webrtc version 45.0 not Opera 32.0. ua = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like ' + 'Gecko) Chrome/45.0.2454.85 Safari/537.36 OPR/32.0.1948.44'; t.equal(m.webrtcUtils.extractVersion(ua, /Chrom(e|ium)\/([0-9]+)\./, 2), 45, 'version extraction'); // Edge, extract build number. ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, ' + 'like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10547'; t.equal(m.webrtcUtils.extractVersion(ua, /Edge\/(\d+).(\d+)$/, 2), 10547, 'version extraction'); // Firefox. ua = 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:44.0) Gecko/20100101 ' + 'Firefox/44.0'; t.equal(m.webrtcUtils.extractVersion(ua, /Firefox\/([0-9]+)\./, 1), 44, 'version extraction'); t.end(); }); test('Browser identified', function(t) { var driver = seleniumHelpers.buildDriver(); // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.plan(4); t.pass('Page loaded'); return driver.executeScript('return webrtcDetectedBrowser'); }) .then(function(webrtcDetectedBrowser) { t.ok(webrtcDetectedBrowser, 'Browser detected: ' + webrtcDetectedBrowser); return driver.executeScript('return webrtcDetectedVersion'); }) .then(function(webrtcDetectVersion) { t.ok(webrtcDetectVersion, 'Browser version detected: ' + webrtcDetectVersion); return driver.executeScript('return webrtcMinimumVersion'); }) .then(function(webrtcMinimumVersion) { t.ok(webrtcMinimumVersion, 'Minimum Browser version detected: ' + webrtcMinimumVersion); t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('Browser supported by adapter.js', function(t) { var driver = seleniumHelpers.buildDriver(); // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.plan(2); t.pass('Page loaded'); }) .then(function() { return driver.executeScript( 'return webrtcDetectedVersion >= webrtcMinimumVersion'); }) .then(function(webrtcVersionIsGreaterOrEqual) { t.ok(webrtcVersionIsGreaterOrEqual, 'Browser version supported by adapter.js'); t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); // Test that getUserMedia is shimmed properly. test('navigator.mediaDevices.getUserMedia', function(t) { var driver = seleniumHelpers.buildDriver(); // Define test. var testDefinition = function() { var callback = arguments[arguments.length - 1]; navigator.mediaDevices.getUserMedia({video: true, fake: true}) .then(function(stream) { window.stream = stream; callback(null); }) .catch(function(err) { callback(err.name); }); }; // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.pass('Page loaded'); return driver.executeAsyncScript(testDefinition); }) .then(function(error) { var gumResult = (error) ? 'error: ' + error : 'no errors'; t.ok(!error, 'getUserMedia result: ' + gumResult); // Make sure we get a stream before continuing. driver.wait(function() { return driver.executeScript( 'return typeof window.stream !== \'undefined\''); }, 3000); }) .then(function() { return driver.wait(function() { return driver.executeScript( 'return window.stream.getVideoTracks().length > 0'); }); }) .then(function(gotVideoTracks) { t.ok(gotVideoTracks, 'Got stream with video tracks.'); }) .then(function() { t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('getUserMedia shim', function(t) { var driver = seleniumHelpers.buildDriver(); // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.plan(3); t.pass('Page loaded'); return driver.executeScript( 'return typeof navigator.getUserMedia !== \'undefined\''); }) .then(function(isGetUserMediaDefined) { t.ok(isGetUserMediaDefined, 'navigator.getUserMedia is defined'); return driver.executeScript( 'return typeof navigator.mediaDevices.getUserMedia !== \'undefined\''); }) .then(function(isMediaDevicesDefined) { t.ok(isMediaDevicesDefined, 'navigator.mediaDevices.getUserMedia is defined'); t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); // Test that adding and removing an eventlistener on navigator.mediaDevices // is possible. The usecase for this is the devicechanged event. // This does not test whether devicechanged is actually called. test('navigator.mediaDevices eventlisteners', function(t) { var driver = seleniumHelpers.buildDriver(); // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.plan(3); t.pass('Page loaded'); return driver.executeScript( 'return typeof(navigator.mediaDevices.addEventListener) === ' + '\'function\''); }) .then(function(isAddEventListenerFunction) { t.ok(isAddEventListenerFunction, 'navigator.mediaDevices.addEventListener is a function'); return driver.executeScript( 'return typeof(navigator.mediaDevices.removeEventListener) === ' + '\'function\''); }) .then(function(isRemoveEventListenerFunction) { t.ok(isRemoveEventListenerFunction, 'navigator.mediaDevices.removeEventListener is a function'); }) .then(function() { t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('RTCPeerConnection shim', function(t) { var driver = seleniumHelpers.buildDriver(); // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.plan(4); t.pass('Page loaded'); return driver.executeScript( 'return window.RTCPeerConnection !== \'undefined\''); }) .then(function(isRTCPeerConnectionDefined) { t.ok(isRTCPeerConnectionDefined, 'RTCPeerConnection is defined'); return driver.executeScript( 'return typeof window.RTCSessionDescription !== \'undefined\''); }) .then(function(isRTCSessionDescriptionDefined) { t.ok(isRTCSessionDescriptionDefined, 'RTCSessionDescription is defined'); return driver.executeScript( 'return typeof window.RTCIceCandidate !== \'undefined\''); }) .then(function(isRTCIceCandidateDefined) { t.ok(isRTCIceCandidateDefined, 'RTCIceCandidate is defined'); t.end(); }) .then(null, function(err) { t.fail(err); t.end(); }); }); test('Create RTCPeerConnection', function(t) { var driver = seleniumHelpers.buildDriver(); // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.plan(2); t.pass('Page loaded'); return driver.executeScript( 'return typeof(new RTCPeerConnection()) === \'object\''); }) .then(function(hasRTCPeerconnectionObjectBeenCreated) { t.ok(hasRTCPeerconnectionObjectBeenCreated, 'RTCPeerConnection constructor'); t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('attachMediaStream', function(t) { var driver = seleniumHelpers.buildDriver(); // Define test. var testDefinition = function() { var callback = arguments[arguments.length - 1]; var constraints = {video: true, fake: true}; navigator.mediaDevices.getUserMedia(constraints) .then(function(stream) { window.stream = stream; var video = document.createElement('video'); video.setAttribute('id', 'video'); video.setAttribute('autoplay', 'true'); // If attachMediaStream works, we should get a video // at some point. This will trigger onresize. // Firefox < 38 had issues with this, workaround removed // due to 38 being stable now. video.addEventListener('resize', function() { document.body.appendChild(video); }); attachMediaStream(video, stream); callback(null); }) .catch(function(err) { callback(err.name); }); }; // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.plan(6); t.pass('Page loaded'); return driver.executeAsyncScript(testDefinition); }) .then(function(error) { var gumResult = (error) ? 'error: ' + error : 'no errors'; t.ok(!error, 'getUserMedia result: ' + gumResult); // We need to wait due to the stream can take a while to setup. driver.wait(function() { return driver.executeScript( 'return typeof window.stream !== \'undefined\''); }, 3000); return driver.executeScript( // Firefox and Chrome have different constructor names. 'return window.stream.constructor.name.match(\'MediaStream\') !== null'); }) .then(function(isMediaStream) { t.ok(isMediaStream, 'Stream is a MediaStream'); // Wait until resize event has fired and appended video element. // 5 second timeout in case the event does not fire for some reason. return driver.wait(webdriver.until.elementLocated( webdriver.By.id('video')), 3000); }) .then(function(videoElement) { t.pass('attachMediaStream successfully attached stream to video element'); videoElement.getAttribute('videoWidth') .then(function(width) { videoElement.getAttribute('videoHeight') .then(function(height) { // Chrome sets the stream dimensions to 2x2 if something is wrong // with the stream/frames from the camera. t.ok(width > 2, 'Video width is: ' + width); t.ok(height > 2, 'Video height is: ' + height); }); }); }) .then(function() { t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('reattachMediaStream', function(t) { var driver = seleniumHelpers.buildDriver(); // Define test. var testDefinition = function() { var callback = arguments[arguments.length - 1]; var constraints = {video: true, fake: true}; navigator.mediaDevices.getUserMedia(constraints) .then(function(stream) { window.stream = stream; var video = document.createElement('video'); var video2 = document.createElement('video'); video.setAttribute('id', 'video'); video.setAttribute('autoplay', 'true'); video2.setAttribute('id', 'video2'); video2.setAttribute('autoplay', 'true'); // If attachMediaStream works, we should get a video // at some point. This will trigger onresize. // This reattaches to the second video which will trigger // onresize there. video.addEventListener('resize', function() { document.body.appendChild(video); reattachMediaStream(video2, video); }); video2.addEventListener('resize', function() { document.body.appendChild(video2); }); attachMediaStream(video, stream); callback(null); }) .catch(function(err) { callback(err.name); }); }; // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.plan(9); t.pass('Page loaded'); return driver.executeAsyncScript(testDefinition); }) .then(function(error) { var gumResult = (error) ? 'error: ' + error : 'no errors'; t.ok(!error, 'getUserMedia result: ' + gumResult); driver.wait(function() { // We need to wait due to the stream can take a while to setup. return driver.executeScript( 'return typeof window.stream !== \'undefined\''); }, 3000); return driver.executeScript( // Firefox and Chrome have different constructor names. 'return window.stream.constructor.name.match(\'MediaStream\') !== null'); }) .then(function(isMediaStream) { t.ok(isMediaStream, 'Stream is a MediaStream'); // Wait until resize event has fired and appended video element. return driver.wait(webdriver.until.elementLocated( webdriver.By.id('video')), 3000); }) .then(function(videoElement) { t.pass('attachMediaStream successfully attached stream to video element'); videoElement.getAttribute('videoWidth') .then(function(width) { videoElement.getAttribute('videoHeight') .then(function(height) { // Chrome sets the stream dimensions to 2x2 if something is wrong // with the stream/frames from the camera. t.ok(width > 2, 'Video width is: ' + width); t.ok(height > 2, 'Video height is: ' + height); }); }); // Wait until resize event has fired and appended video element. return driver.wait(webdriver.until.elementLocated( webdriver.By.id('video2')), 3000); }) .then(function(videoElement2) { t.pass('attachMediaStream succesfully re-attached stream to video element'); videoElement2.getAttribute('videoWidth') .then(function(width) { videoElement2.getAttribute('videoHeight') .then(function(height) { // Chrome sets the stream dimensions to 2x2 if something is wrong // with the stream/frames from the camera. t.ok(width > 2, 'Video 2 width is: ' + width); t.ok(height > 2, 'Video 2 height is: ' + height); }); }); }) .then(function() { t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('Video srcObject getter/setter test', function(t) { var driver = seleniumHelpers.buildDriver(); // Define test. var testDefinition = function() { var callback = arguments[arguments.length - 1]; var constraints = {video: true, fake: true}; navigator.mediaDevices.getUserMedia(constraints) .then(function(stream) { window.stream = stream; var video = document.createElement('video'); video.setAttribute('id', 'video'); video.setAttribute('autoplay', 'true'); video.srcObject = stream; // If attachMediaStream works, we should get a video // at some point. This will trigger onresize. video.addEventListener('resize', function() { document.body.appendChild(video); }); callback(null); }) .catch(function(err) { callback(err.name); }); }; // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.plan(3); t.pass('Page loaded'); return driver.executeAsyncScript(testDefinition); }) .then(function(error) { var gumResult = (error) ? 'error: ' + error : 'no errors'; t.ok(!error, 'getUserMedia result: ' + gumResult); // Wait until resize event has fired and appended video element. return driver.wait(webdriver.until.elementLocated( webdriver.By.id('video')), 3000); }) .then(function() { return driver.executeScript( 'return document.getElementById(\'video\').srcObject.id') .then(function(srcObjectId) { return srcObjectId; }) .then(function(srcObjectId) { driver.executeScript('return window.stream.id') .then(function(streamId) { t.ok(srcObjectId === streamId, 'srcObject getter returns stream object'); }); }); }) .then(function() { t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('Audio srcObject getter/setter test', function(t) { var driver = seleniumHelpers.buildDriver(); // Define test. var testDefinition = function() { var callback = arguments[arguments.length - 1]; var constraints = {video: false, audio: true, fake: true}; navigator.mediaDevices.getUserMedia(constraints) .then(function(stream) { window.stream = stream; var audio = document.createElement('audio'); audio.setAttribute('id', 'audio'); audio.srcObject = stream; // If attachMediaStream works, we should get a video // at some point. This will trigger onresize. audio.addEventListener('loadedmetadata', function() { document.body.appendChild(audio); }); callback(null); }) .catch(function(err) { callback(err.name); }); }; // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.plan(3); t.pass('Page loaded'); return driver.executeAsyncScript(testDefinition); }) .then(function(error) { var gumResult = (error) ? 'error: ' + error : 'no errors'; t.ok(!error, 'getUserMedia result: ' + gumResult); // Wait until resize event has fired and appended video element. // 5 second timeout in case the event does not fire for some reason. return driver.wait(webdriver.until.elementLocated( webdriver.By.id('audio')), 3000); }) .then(function() { return driver.executeScript( 'return document.getElementById(\'audio\').srcObject.id') .then(function(srcObjectId) { driver.executeScript('return window.stream.id') .then(function(streamId) { t.ok(srcObjectId === streamId, 'srcObject getter returns stream object'); }); }); }) .then(function() { t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('srcObject set from another object', function(t) { var driver = seleniumHelpers.buildDriver(); // Define test. var testDefinition = function() { var callback = arguments[arguments.length - 1]; var constraints = {video: true, fake: true}; navigator.mediaDevices.getUserMedia(constraints) .then(function(stream) { window.stream = stream; var video = document.createElement('video'); var video2 = document.createElement('video2'); video.setAttribute('id', 'video'); video.setAttribute('autoplay', 'true'); video2.setAttribute('id', 'video2'); video2.setAttribute('autoplay', 'true'); video.srcObject = stream; video2.srcObject = video.srcObject; // If attachMediaStream works, we should get a video // at some point. This will trigger onresize. video.addEventListener('resize', function() { document.body.appendChild(video); document.body.appendChild(video2); }); callback(null); }) .catch(function(err) { callback(err.name); }); }; // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.plan(3); t.pass('Page loaded'); return driver.executeAsyncScript(testDefinition); }) .then(function(error) { var gumResult = (error) ? 'error: ' + error : 'no errors'; t.ok(!error, 'getUserMedia result: ' + gumResult); // Wait until resize event has fired and appended video element. // 5 second timeout in case the event does not fire for some reason. return driver.wait(webdriver.until.elementLocated( webdriver.By.id('video2')), 3000); }) .then(function() { return driver.executeScript( 'return document.getElementById(\'video\').srcObject.id') .then(function(srcObjectId) { driver.executeScript( 'return document.getElementById(\'video2\').srcObject.id') .then(function(srcObjectId2) { t.ok(srcObjectId === srcObjectId2, 'Stream ids from srcObjects match.'); }); }); }) .then(function() { t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('Attach mediaStream directly', function(t) { var driver = seleniumHelpers.buildDriver(); // Define test. var testDefinition = function() { var callback = arguments[arguments.length - 1]; var constraints = {video: true, fake: true}; navigator.mediaDevices.getUserMedia(constraints) .then(function(stream) { window.stream = stream; var video = document.createElement('video'); video.setAttribute('id', 'video'); video.setAttribute('autoplay', 'true'); // If attachMediaStream works, we should get a video // at some point. This will trigger onresize. // Firefox < 38 had issues with this, workaround removed // due to 38 being stable now. video.addEventListener('resize', function() { document.body.appendChild(video); }); video.srcObject = stream; callback(null); }) .catch(function(err) { callback(err.name); }); }; // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.plan(6); t.pass('Page loaded'); return driver.executeAsyncScript(testDefinition); }) .then(function(error) { var gumResult = (error) ? 'error: ' + error : 'no errors'; t.ok(!error, 'getUserMedia result: ' + gumResult); // We need to wait due to the stream can take a while to setup. driver.wait(function() { return driver.executeScript( 'return typeof window.stream !== \'undefined\''); }, 3000); return driver.executeScript( // Firefox and Chrome have different constructor names. 'return window.stream.constructor.name.match(\'MediaStream\') !== null'); }) .then(function(isMediaStream) { t.ok(isMediaStream, 'Stream is a MediaStream'); // Wait until resize event has fired and appended video element. // 5 second timeout in case the event does not fire for some reason. return driver.wait(webdriver.until.elementLocated( webdriver.By.id('video')), 3000); }) .then(function(videoElement) { t.pass('Stream attached directly succesfully to a video element'); videoElement.getAttribute('videoWidth') .then(function(width) { videoElement.getAttribute('videoHeight') .then(function(height) { // Chrome sets the stream dimensions to 2x2 if something is wrong // with the stream/frames from the camera. t.ok(width > 2, 'Video width is: ' + width); t.ok(height > 2, 'Video height is: ' + height); }); }); }) .then(function() { t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('Re-attaching mediaStream directly', function(t) { var driver = seleniumHelpers.buildDriver(); // Define test. var testDefinition = function() { var callback = arguments[arguments.length - 1]; var constraints = {video: true, fake: true}; navigator.mediaDevices.getUserMedia(constraints) .then(function(stream) { window.stream = stream; var video = document.createElement('video'); var video2 = document.createElement('video'); video.setAttribute('id', 'video'); video.setAttribute('autoplay', 'true'); video2.setAttribute('id', 'video2'); video2.setAttribute('autoplay', 'true'); // If attachMediaStream works, we should get a video // at some point. This will trigger onresize. // This reattaches to the second video which will trigger // onresize there. video.addEventListener('resize', function() { document.body.appendChild(video); video2.srcObject = video.srcObject; }); video2.addEventListener('resize', function() { document.body.appendChild(video2); }); video.srcObject = stream; callback(null); }) .catch(function(err) { callback(err.name); }); }; // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.plan(9); t.pass('Page loaded'); return driver.executeAsyncScript(testDefinition); }) .then(function(error) { var gumResult = (error) ? 'error: ' + error : 'no errors'; t.ok(!error, 'getUserMedia result: ' + gumResult); // We need to wait due to the stream can take a while to setup. return driver.wait(function() { return driver.executeScript( 'return typeof window.stream !== \'undefined\''); }, 3000) .then(function() { return driver.executeScript( // Firefox and Chrome have different constructor names. 'return window.stream.constructor.name.match(\'MediaStream\') !== null'); }); }) .then(function(isMediaStream) { t.ok(isMediaStream, 'Stream is a MediaStream'); // Wait until resize event has fired and appended video element. // 5 second timeout in case the event does not fire for some reason. return driver.wait(webdriver.until.elementLocated( webdriver.By.id('video')), 3000); }) .then(function(videoElement) { t.pass('Stream attached directly succesfully to a video element'); videoElement.getAttribute('videoWidth') .then(function(width) { videoElement.getAttribute('videoHeight') .then(function(height) { // Chrome sets the stream dimensions to 2x2 if something is wrong // with the stream/frames from the camera. t.ok(width > 2, 'Video width is: ' + width); t.ok(height > 2, 'Video height is: ' + height); }); }); // Wait until resize event has fired and appended video element. // 5 second timeout in case the event does not fire for some reason. return driver.wait(webdriver.until.elementLocated( webdriver.By.id('video2')), 3000); }) .then(function(videoElement2) { t.pass('Stream re-attached directly succesfully to a video element'); videoElement2.getAttribute('videoWidth') .then(function(width) { videoElement2.getAttribute('videoHeight') .then(function(height) { // Chrome sets the stream dimensions to 2x2 if something is wrong // with the stream/frames from the camera. t.ok(width > 2, 'Video 2 width is: ' + width); t.ok(height > 2, 'Video 2 height is: ' + height); }); }); }) .then(function() { t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('Call getUserMedia with impossible constraints', function(t) { var driver = seleniumHelpers.buildDriver(); // Define test. var testDefinition = function() { var callback = arguments[arguments.length - 1]; var impossibleConstraints = { video: { width: 1280, height: {min: 200, ideal: 720, max: 1080}, frameRate: {exact: 0} // to fail } }; // TODO: Remove when firefox 42+ accepts impossible constraints // on fake devices. if (webrtcDetectedBrowser === 'firefox') { impossibleConstraints.fake = false; } navigator.mediaDevices.getUserMedia(impossibleConstraints) .then(function(stream) { window.stream = stream; callback(null); }) .catch(function(err) { callback(err.name); }); }; // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.plan(2); t.pass('Page loaded'); return driver.executeScript( 'return webrtcDetectedBrowser === \'firefox\' ' + '&& webrtcDetectedVersion < 42'); }) .then(function(isFirefoxAndVersionLessThan42) { if (isFirefoxAndVersionLessThan42) { t.skip('getUserMedia(impossibleConstraints) not supported on < 42'); throw 'skip-test'; } return driver.executeAsyncScript(testDefinition); }) .then(function(error) { t.ok(error, 'getUserMedia(impossibleConstraints) must fail'); }) .then(function() { t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('Check getUserMedia legacy constraints converter', function(t) { var driver = seleniumHelpers.buildDriver(); var testDefinition = function() { // Used to collect the result of test. window.constraintsArray = []; // Helpers to test adapter's legacy constraints-manipulation. function pretendVersion(version, func) { var realVersion = webrtcDetectedVersion; window.webrtcTesting.version = version; func(); window.webrtcTesting.version = realVersion; } function interceptGumForConstraints(gum, func) { var origGum = navigator[gum].bind(navigator); var netConstraints; navigator[gum] = function(constraints) { netConstraints = constraints; }; func(); navigator[gum] = origGum; return netConstraints; } function testBeforeAfterPairs(gum, pairs) { pairs.forEach(function(beforeAfter, counter) { var constraints = interceptGumForConstraints(gum, function() { navigator.getUserMedia(beforeAfter[0], function() {}, function() {}); }); window.constraintsArray.push([constraints, beforeAfter[1], gum, counter + 1]); }); } var testFirefox = function() { pretendVersion(37, function() { testBeforeAfterPairs('mozGetUserMedia', [ // Test that spec constraints get back-converted on FF37. [ { video: { mediaSource: 'screen', width: 1280, height: {min: 200, ideal: 720, max: 1080}, facingMode: 'user', frameRate: {exact: 50} } }, { video: { mediaSource: 'screen', height: {min: 200, max: 1080}, frameRate: {max: 50, min: 50}, advanced: [ {width: {min: 1280, max: 1280}}, {height: {min: 720, max: 720}}, {facingMode: 'user'} ], require: ['height', 'frameRate'] } } ], // Test that legacy constraints pass through unharmed on FF37. [ { video: { height: {min: 200, max: 1080}, frameRate: {max: 50, min: 50}, advanced: [ {width: {min: 1280, max: 1280}}, {height: {min: 720, max: 720}}, {facingMode: 'user'} ], require: ['height', 'frameRate'] } }, { video: { height: {min: 200, max: 1080}, frameRate: {max: 50, min: 50}, advanced: [ {width: {min: 1280, max: 1280}}, {height: {min: 720, max: 720}}, {facingMode: 'user'} ], require: ['height', 'frameRate'] } } ], ]); }); pretendVersion(38, function() { testBeforeAfterPairs('mozGetUserMedia', [ // Test that spec constraints pass through unharmed on FF38+. [ { video: { mediaSource: 'screen', width: 1280, height: {min: 200, ideal: 720, max: 1080}, facingMode: 'user', frameRate: {exact: 50} } }, { video: { mediaSource: 'screen', width: 1280, height: {min: 200, ideal: 720, max: 1080}, facingMode: 'user', frameRate: {exact: 50} } }, ], ]); }); }; var testChrome = function() { testBeforeAfterPairs('webkitGetUserMedia', [ // Test that spec constraints get back-converted on Chrome. [ { video: { width: 1280, height: {min: 200, ideal: 720, max: 1080}, frameRate: {exact: 50} } }, { video: { mandatory: { maxFrameRate: 50, maxHeight: 1080, minHeight: 200, minFrameRate: 50 }, optional: [ {minWidth: 1280}, {maxWidth: 1280}, {minHeight: 720}, {maxHeight: 720}, ] } } ], // Test that legacy constraints pass through unharmed on Chrome. [ { video: { mandatory: { maxFrameRate: 50, maxHeight: 1080, minHeight: 200, minFrameRate: 50 }, optional: [ {minWidth: 1280}, {maxWidth: 1280}, {minHeight: 720}, {maxHeight: 720}, ] } }, { video: { mandatory: { maxFrameRate: 50, maxHeight: 1080, minHeight: 200, minFrameRate: 50 }, optional: [ {minWidth: 1280}, {maxWidth: 1280}, {minHeight: 720}, {maxHeight: 720}, ] } } ], // Test code protecting Chrome from choking on common unknown constraints. [ { video: { mediaSource: 'screen', advanced: [ {facingMode: 'user'} ], require: ['height', 'frameRate'] } }, { video: { optional: [ {facingMode: 'user'} ] } } ] ]); }; // Since this test has specific constraints/functions per browser, the // decision if the test should be run or not is in the Test definition // rather than the preferred Run test section (Webdriver). // FIXME: Move the decision to // Run test. if (webrtcDetectedBrowser === 'chrome') { testChrome(); } else if (webrtcDetectedBrowser === 'firefox') { testFirefox(); } else { return window.constraintsArray.push('Unsupported browser'); } }; // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { // t.plan(2); t.pass('Page loaded'); return driver.executeScript(testDefinition) .then(function() { return driver.executeScript('return window.constraintsArray'); }); }) .then(function(constraintsArray) { if (constraintsArray[0] === 'Unsupported browser') { // Skipping if the browser is not supported. t.skip(constraintsArray); throw 'skip-test'; } // constraintsArray[constr][0] = Constraints to adapter.js. // constraintsArray[constr][1] = Constraints from adapter.js. // constraintsArray[constr][2] = Constraint pair counter. // constraintsArray[constr][3] = getUserMedia API called. for (var constr = 0; constr < constraintsArray.length; constr++) { t.deepEqual(constraintsArray[constr][0], constraintsArray[constr][1], 'Constraints ' + constraintsArray[constr][3] + ' back-converted to: ' + constraintsArray[constr][2]); } }) .then(function() { t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('Basic connection establishment', function(t) { var driver = seleniumHelpers.buildDriver(); var testDefinition = function() { var callback = arguments[arguments.length - 1]; var counter = 1; window.testPassed = []; window.testFailed = []; var t = { ok: function(ok, msg) { window[ok ? 'testPassed' : 'testFailed'].push(msg); }, is: function(a, b, msg) { this.ok((a === b), msg + ' - got ' + b); }, pass: function(msg) { this.ok(true, msg); }, fail: function(msg) { this.ok(false, msg); } }; var pc1 = new RTCPeerConnection(null); var pc2 = new RTCPeerConnection(null); pc1.oniceconnectionstatechange = function() { if (pc1.iceConnectionState === 'connected' || pc1.iceConnectionState === 'completed') { callback(pc1.iceConnectionState); } }; var addCandidate = function(pc, event) { if (event.candidate) { var cand = new RTCIceCandidate(event.candidate); pc.addIceCandidate(cand, function() { // TODO: Decide if we are intereted in adding all candidates // as passed tests. t.pass('addIceCandidate ' + counter++); }, function(err) { t.fail('addIceCandidate ' + err.toString()); } ); } }; pc1.onicecandidate = function(event) { addCandidate(pc2, event); }; pc2.onicecandidate = function(event) { addCandidate(pc1, event); }; pc2.ontrack = function(e) { t.ok(true, 'pc2.ontrack'); t.ok(typeof e.track === 'object', 'trackEvent.track is an object'); t.ok(typeof e.receiver === 'object', 'trackEvent.receiver is object'); t.ok(Array.isArray(e.streams), 'trackEvent.streams is an array'); t.is(e.streams.length, 1, 'trackEvent.streams has one stream'); t.ok(e.streams[0].getTracks().indexOf(e.track) !== -1, 'trackEvent.track is in stream'); var receivers = pc2.getReceivers(); if (receivers && receivers.length) { t.ok(receivers.indexOf(e.receiver) !== -1, 'trackEvent.receiver matches a known receiver'); } }; var constraints = {video: true, fake: true}; navigator.mediaDevices.getUserMedia(constraints) .then(function(stream) { pc1.addStream(stream); pc1.createOffer( function(offer) { t.pass('pc1.createOffer'); pc1.setLocalDescription(offer, function() { t.pass('pc1.setLocalDescription'); offer = new RTCSessionDescription(offer); t.pass('created RTCSessionDescription from offer'); pc2.setRemoteDescription(offer, function() { t.pass('pc2.setRemoteDescription'); pc2.createAnswer( function(answer) { t.pass('pc2.createAnswer'); pc2.setLocalDescription(answer, function() { t.pass('pc2.setLocalDescription'); answer = new RTCSessionDescription(answer); t.pass('created RTCSessionDescription from answer'); pc1.setRemoteDescription(answer, function() { t.pass('pc1.setRemoteDescription'); }, function(err) { t.pass('pc1.setRemoteDescription ' + err.toString()); } ); }, function(err) { t.fail('pc2.setLocalDescription ' + err.toString()); } ); }, function(err) { t.fail('pc2.createAnswer ' + err.toString()); } ); }, function(err) { t.fail('pc2.setRemoteDescription ' + err.toString()); } ); }, function(err) { t.fail('pc1.setLocalDescription ' + err.toString()); } ); }, function(err) { t.fail('pc1 failed to create offer ' + err.toString()); } ); }); }; // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.pass('Page loaded'); return driver.executeAsyncScript(testDefinition); }) .then(function(pc1ConnectionStatus) { t.ok(pc1ConnectionStatus === 'completed' || 'connected', 'P2P connection established'); return driver.executeScript('return window.testPassed'); }) .then(function(testPassed) { return driver.executeScript('return window.testFailed') .then(function(testFailed) { for (var testPass = 0; testPass < testPassed.length; testPass++) { t.pass(testPassed[testPass]); } for (var testFail = 0; testFail < testFailed.length; testFail++) { t.fail(testFailed[testFail]); } }); }) .then(function() { t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('Basic connection establishment with promise', function(t) { var driver = seleniumHelpers.buildDriver(); var testDefinition = function() { var callback = arguments[arguments.length - 1]; var counter = 1; window.testPassed = []; window.testFailed = []; var t = { ok: function(ok, msg) { window[ok ? 'testPassed' : 'testFailed'].push(msg); }, is: function(a, b, msg) { this.ok((a === b), msg + ' - got ' + b); }, pass: function(msg) { this.ok(true, msg); }, fail: function(msg) { this.ok(false, msg); } }; var pc1 = new RTCPeerConnection(null); var pc2 = new RTCPeerConnection(null); pc1.oniceconnectionstatechange = function() { if (pc1.iceConnectionState === 'connected' || pc1.iceConnectionState === 'completed') { callback(pc1.iceConnectionState); } }; var addCandidate = function(pc, event) { if (event.candidate) { var cand = new RTCIceCandidate(event.candidate); pc.addIceCandidate(cand).then(function() { // TODO: Decide if we are interested in adding all candidates // as passed tests. t.pass('addIceCandidate ' + counter++); }) .catch(function(err) { t.fail('addIceCandidate ' + err.toString()); }); } }; pc1.onicecandidate = function(event) { addCandidate(pc2, event); }; pc2.onicecandidate = function(event) { addCandidate(pc1, event); }; var constraints = {video: true, fake: true}; navigator.mediaDevices.getUserMedia(constraints) .then(function(stream) { pc1.addStream(stream); pc1.createOffer().then(function(offer) { t.pass('pc1.createOffer'); return pc1.setLocalDescription(offer); }).then(function() { t.pass('pc1.setLocalDescription'); return pc2.setRemoteDescription(pc1.localDescription); }).then(function() { t.pass('pc2.setRemoteDescription'); return pc2.createAnswer(); }).then(function(answer) { t.pass('pc2.createAnswer'); return pc2.setLocalDescription(answer); }).then(function() { t.pass('pc2.setLocalDescription'); return pc1.setRemoteDescription(pc2.localDescription); }).then(function() { t.pass('pc1.setRemoteDescription'); }).catch(function(err) { window.testfailed.push(err.toString()); }); }) .catch(function(error) { callback(error); }); }; // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.pass('Page loaded'); return driver.executeAsyncScript(testDefinition); }) .then(function(callback) { // Callback will either return an error object or pc1ConnectionStatus. if (callback.name === 'Error') { t.fail('getUserMedia failure: ' + callback.toString()); } else { return callback; } }) .then(function(pc1ConnectionStatus) { t.ok(pc1ConnectionStatus === 'completed' || 'connected', 'P2P connection established'); return driver.executeScript('return window.testPassed'); }) .then(function(testPassed) { return driver.executeScript('return window.testFailed') .then(function(testFailed) { for (var testPass = 0; testPass < testPassed.length; testPass++) { t.pass(testPassed[testPass]); } for (var testFail = 0; testFail < testFailed.length; testFail++) { t.fail(testFailed[testFail]); } }); }) .then(function() { t.end(); }) .then(null, function(err) { if (err !== 'skip-test') { t.fail(err); } t.end(); }); }); test('Basic connection establishment with datachannel', function(t) { var driver = seleniumHelpers.buildDriver(); var testDefinition = function() { var callback = arguments[arguments.length - 1]; var counter = 1; window.testPassed = []; window.testFailed = []; var t = { ok: function(ok, msg) { window[ok ? 'testPassed' : 'testFailed'].push(msg); }, is: function(a, b, msg) { this.ok((a === b), msg + ' - got ' + b); }, pass: function(msg) { this.ok(true, msg); }, fail: function(msg) { this.ok(false, msg); } }; var pc1 = new RTCPeerConnection(null); var pc2 = new RTCPeerConnection(null); if (typeof pc1.createDataChannel !== 'function') { callback('DataChannel is not supported'); return; } pc1.oniceconnectionstatechange = function() { if (pc1.iceConnectionState === 'connected' || pc1.iceConnectionState === 'completed') { callback(pc1.iceConnectionState); } }; var addCandidate = function(pc, event) { if (event.candidate) { var cand = new RTCIceCandidate(event.candidate); pc.addIceCandidate(cand).then(function() { // TODO: Decide if we are interested in adding all candidates // as passed tests. t.pass('addIceCandidate ' + counter++); }) .catch(function(err) { t.fail('addIceCandidate ' + err.toString()); }); } }; pc1.onicecandidate = function(event) { addCandidate(pc2, event); }; pc2.onicecandidate = function(event) { addCandidate(pc1, event); }; pc1.createDataChannel('somechannel'); pc1.createOffer().then(function(offer) { t.pass('pc1.createOffer'); return pc1.setLocalDescription(offer); }).then(function() { t.pass('pc1.setLocalDescription'); return pc2.setRemoteDescription(pc1.localDescription); }).then(function() { t.pass('pc2.setRemoteDescription'); return pc2.createAnswer(); }).then(function(answer) { t.pass('pc2.createAnswer'); return pc2.setLocalDescription(answer); }).then(function() { t.pass('pc2.setLocalDescription'); return pc1.setRemoteDescription(pc2.localDescription); }).then(function() { t.pass('pc1.setRemoteDescription'); }).catch(function(err) { t.fail(err.name); }); }; // Run test. driver.get('file://' + process.cwd() + '/test/testpage.html') .then(function() { t.pass('Page loaded'); return driver.executeAsyncScript(testDefinition); }) .then(function(callback) { // Callback will either return DataChannel not supported // or pc1ConnectionStatus. if (callback === 'DataChannel