2019/functional/functionalTest.js (1,167 lines of code) (raw):

/** * @license * Copyright 2018 Google Inc. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; /** * Functional Test Suite. * @class */ var FunctionalTest = function() { const URL_PATH = 'lib/functional/src/'; const MEDIA_PATH = 'test-materials/media/'; const MAX_URL_LENGTH = 2047; const TEST_MEDIA_SRC = { Jpg: MEDIA_PATH + 'qual-e/pass.jpg', Png: MEDIA_PATH + 'qual-e/pass.png', Webp: MEDIA_PATH + 'qual-e/pass.webp', SslTestVideoMp4: MEDIA_PATH + 'qual-e/sslTestVideo.mp4', SslTestVideoWebm: MEDIA_PATH + 'qual-e/sslTestVideo.webm', }; const MS_TO_WAIT_FOR_FONTS = 3000; const COBALT_REQ_URL = 'https://cobalt.googlesource.com/cobalt/+/19.lts.stable/' + 'src/cobalt/build/build.id?format=TEXT'; var functionalVersion = 'Current Editor\'s Draft'; var webkitPrefix = MediaSource.prototype.version.indexOf('webkit') >= 0; var tests = []; var testArea = document.getElementById('testArea'); var info = 'No Media Support!'; if (window.MediaSource) { info = 'Spec Version: ' + functionalVersion; info += ' | webkit prefix: ' + webkitPrefix.toString(); } info += ' | Default Timeout: ' + TestBase.timeout + 'ms'; var fields = ['passes', 'failures', 'timeouts']; var createFunctionalTest = function(name, category, mandatory) { var t = createTest(name); t.prototype.index = tests.length; t.prototype.passes = 0; t.prototype.failures = 0; t.prototype.timeouts = 0; t.prototype.category = category || 'Functional'; if (typeof mandatory === 'boolean') { t.prototype.mandatory = mandatory; } tests.push(t); return t; }; // Create and check if the element is supported by browser var createElement = function(elementName) { var element = document.createElement(elementName); if (Object.prototype.toString.call(element) === '[object HTMLUnknownElement]') { throw Error(elementName + ' not supported!'); } return element; }; /** * Validate the existence of AudioContext and its properties. */ var createAudioContextPropertyTest = function(value, evaluate) { var name = value ? 'AudioContext.' + value : 'AudioContext'; var test = createFunctionalTest(name,'AudioContext'); test.prototype.title = 'Test if' + name + ' exists'; test.prototype.start = function(runner) { try { var audioCtx = new (window.AudioContext || window.webkitAudioContext)(); if (!evaluate(audioCtx)) { throw 'No ' + name + '.'; } } catch (e) { return runner.fail(e); } runner.succeed(); } }; createAudioContextPropertyTest(null, function(audioCtx) { return !!(audioCtx); }); createAudioContextPropertyTest('destination', function(audioCtx) { return !!(audioCtx.destination); }); createAudioContextPropertyTest('sampleRate', function(audioCtx) { return typeof audioCtx.sampleRate === 'number'; }); createAudioContextPropertyTest('currentTime', function(audioCtx) { return isNaN(audioCtx.currentTime) == false; }); createAudioContextPropertyTest('decodeAudioData', function(audioCtx) { return !!(audioCtx.decodeAudioData); }); createAudioContextPropertyTest('createBufferSource', function(audioCtx) { return (!!audioCtx.createBufferSource && audioCtx.createBufferSource() instanceof AudioBufferSourceNode); }); /** * Validate the existence of AudioBufferSourceNode and its properties. */ var createAudioBufferSourceNodeTest = function(value, evaluate, mandatory) { var name = value ? 'AudioBufferSourceNode.' + value : 'AudioBufferSourceNode'; var test = createFunctionalTest(name, 'AudioBufferSourceNode', mandatory); test.prototype.title = 'Test if ' + name + ' exists.'; test.prototype.start = function(runner) { try { var audioCtx = new (window.AudioContext || window.webkitAudioContext)(); var source = audioCtx.createBufferSource(); if (!evaluate(source)) { throw 'No ' + name + '.'; } } catch (e) { return runner.fail(e); } runner.succeed(); } }; createAudioBufferSourceNodeTest(null, function(source) { return !!source; }); createAudioBufferSourceNodeTest('buffer', function(source) { return !!(typeof source.buffer); }); createAudioBufferSourceNodeTest('onended', function(source) { return !!(typeof source.onended); }); createAudioBufferSourceNodeTest( 'playbackRate', function(source) { return (typeof source.playbackRate.defaultValue === 'number') && (typeof source.playbackRate.value === 'number'); }, false); createAudioBufferSourceNodeTest('start', function(source) { return !!(typeof source.start); }); createAudioBufferSourceNodeTest('stop', function(source) { return !!(typeof source.stop); }); /** * Validate the existence of AudioNode and its properties. */ var createAudioNodeTest = function(value, evaluate) { var name = value ? 'AudioNode.' + value : 'AudioNode'; var test = createFunctionalTest(name, 'AudioNode'); test.prototype.title = 'Test if ' + name + ' exists.'; test.prototype.start = function(runner) { try { var audioCtx = new (window.AudioContext || window.webkitAudioContext)(); var node = audioCtx.destination; if (!evaluate(node, audioCtx)) { throw 'No ' + name + '.'; } } catch (e) { return runner.fail(e); } runner.succeed(); } }; createAudioNodeTest(null, function(node) { return !!(node); }); createAudioNodeTest('context', function(node, audioCtx) { return node.context === audioCtx; }); createAudioNodeTest('numberOfInputs', function(node) { return typeof node.numberOfInputs === 'number'; }); createAudioNodeTest('numberOfOutputs', function(node) { return typeof node.numberOfOutputs === 'number'; }); createAudioNodeTest('channelCountMode', function(node) { return typeof node.channelCountMode === 'string'; }); createAudioNodeTest('channelInterpretation', function(node) { return typeof node.channelInterpretation === 'string'; }); createAudioNodeTest('connect', function(node, audioCtx) { return (!!(audioCtx.createBufferSource().connect)); }); createAudioNodeTest('disconnect', function(node, audioCtx) { return (!!(audioCtx.createBufferSource().disconnect)); }); // expand url into MAX length var expandUrl = function(url, charset) { return url + new Array(MAX_URL_LENGTH - url.length - 1).join(charset); } /** * Ensure super long navigation URL is properly supported. */ var testNavigationURLLength = createFunctionalTest( 'NavigationURLLength', 'URL Length', false); testNavigationURLLength.prototype.title = 'Test Navigation URL Length support.'; testNavigationURLLength.prototype.start = function(runner) { try { var navElement = createElement('iframe'); var randChar = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' .charAt(Math.floor(Math.random() * 62)); var url = expandUrl(URL_PATH + 'navUrlTest.html?q=', randChar); navElement.setAttribute('src', url); navElement.setAttribute('id', 'urlTestFrame' + randChar); testArea.appendChild(navElement); var frame = document.getElementById('urlTestFrame' + randChar); frame.onload = function() { var doc = frame.contentDocument || frame.contentWindow.document || frame.document; var result = doc.getElementsByTagName('title')[0].innerHTML; this.log('Result: ' + result) testArea.removeChild(navElement); if (result == 'Success') { return runner.succeed(); } throw Error(result); }.bind(this); } catch (e) { return runner.fail(e); } }; /** * Ensure super long image src URL is properly supported. */ var testImageSrcURLLength = createFunctionalTest( 'ImageSrcURLLength', 'URL Length'); testImageSrcURLLength.prototype.title = 'Test Image Src URL Length support.'; testImageSrcURLLength.prototype.start = function(runner) { try { var imgElement = createElement('img'); var url = expandUrl(TEST_MEDIA_SRC.Jpg + '?q=', 'a'); imgElement.setAttribute('src', url); testArea.appendChild(imgElement); imgElement.onload = function() { testArea.removeChild(imgElement); imgElement = null; return runner.succeed(); }.bind(this); } catch (e) { return runner.fail(e); } }; /** * Ensure super long video src URL is properly supported. */ var createVideoSrcURLTest = function(format, src) { var test = createFunctionalTest( 'VideoSrcURLLength - ' + format, 'URL Length'); test.prototype.title = 'Test ' + format + ' Video Src URL Length support.'; test.prototype.start = function(runner, video) { try { var randChar = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' .charAt(Math.floor(Math.random() * 62)); var longUrl = expandUrl(src + '?q=', randChar); video.setAttribute('src', longUrl); video.onplaying = function onPlaying() { video.removeEventListener('onplaying', onPlaying); return runner.succeed(); }.bind(this); video.onerror = function() { var message = String().concat(this.name + ' - value returned: ', false); throw Error(message); }.bind(this); video.play(); } catch (e) { return runner.fail(e); } } }; createVideoSrcURLTest('mp4', TEST_MEDIA_SRC.SslTestVideoMp4); /** * Ensure super long XHR request URL is properly supported. */ var testXHRURLLength = createFunctionalTest('XHRURLLength', 'URL Length'); testXHRURLLength.prototype.title = 'Test XHR Src URL Length support.'; testXHRURLLength.prototype.start = function(runner) { try { var xmlHttp = new XMLHttpRequest(); var url = expandUrl(TEST_MEDIA_SRC.Png + '?q=', 'a'); xmlHttp.open('GET', url, true); xmlHttp.send(null); xmlHttp.onreadystatechange = function() { if (xmlHttp.readyState == 4) { if (xmlHttp.status == 200) { return runner.succeed(); } else { runner.fail(Error('Xhr request status is not 200 OK')); } } }; } catch (e) { return runner.fail(e); } }; /** * Validate the existence of CORS through Modernizr. */ var testCORS = createFunctionalTest('CORS', 'Security'); testCORS.prototype.title = 'Test if CORS works.'; testCORS.prototype.start = function(runner) { try { if (window.Modernizr.cors) { return runner.succeed(); } throw Error('CORS is not supported.'); } catch (e) { return runner.fail(e); } }; /** * Validate the existence of localStorage properties. */ var createLocalStorageTest = function(value, evaluate, cleanup) { var name = value ? 'localStorage.' + value : 'localStorage'; var test = createFunctionalTest(name, 'localStorage'); test.prototype.title = 'Test if ' + name + ' works.'; test.prototype.start = function(runner) { try { if (evaluate()) { return runner.succeed(); } throw name + ' errors'; } catch (e) { if (cleanup) { cleanup(); } return runner.fail(e); } } }; createLocalStorageTest(null, function() { return Modernizr.localstorage; }); createLocalStorageTest('setItem', function() { return Modernizr.prefixed('setItem', window.localStorage , false) != false; }); createLocalStorageTest('getItem', function() { return Modernizr.prefixed('getItem', window.localStorage , false) != false; }); createLocalStorageTest('removeItem', function() { return Modernizr.prefixed('removeItem', window.localStorage , false) != false; }); createLocalStorageTest( '1M Char Quota', function() { var testData = new Array(1000 * 1000).join('2'); localStorage.setItem('yt-quale', testData); localStorage.removeItem('yt-quale'); return true; }, function() { localStorage.removeItem('yt-quale'); }); /** * Test if HTTPS onload fires. */ var testHTTPS = createFunctionalTest('HTTPS', 'HTTP'); testHTTPS.prototype.title = 'Test if HTTPS onload fires.'; testHTTPS.prototype.start = function(runner) { try { var sslImg = createElement('img'); sslImg.setAttribute( 'src', 'https://www.google.com/images/srpr/logo3w.png'); testArea.appendChild(sslImg); sslImg.onload = function() { testArea.removeChild(sslImg); sslImg = null; return runner.succeed(); }.bind(this); } catch (e) { return runner.fail(e); } }; /** * Test if XMLHTTPRequest works. */ var testXMLHTTPRequest = createFunctionalTest('XMLHTTPRequest', 'HTTP'); testXMLHTTPRequest.prototype.title = 'Test if XMLHTTPRequest works.'; testXMLHTTPRequest.prototype.start = function(runner) { try { if (new XMLHttpRequest()) { return runner.succeed(); } } catch (e) { return runner.fail(e); } }; /** * Test if XMLHTTPRequest Level 2 works. */ var testXMLHTTPRequestLevel2 = createFunctionalTest( 'XMLHTTPRequest Level 2', 'HTTP'); testXMLHTTPRequestLevel2.prototype.title = 'Test if XMLHTTPRequest Level 2 works.'; testXMLHTTPRequestLevel2.prototype.start = function(runner) { try { // test from https://github.com/NielsLeenheer/html5test/pull/100 if (window.XMLHttpRequest && ('upload' in new XMLHttpRequest())) { return runner.succeed(); } throw Error('Does not support XMLHTTPRequest Level 2'); } catch (e) { return runner.fail(e); } }; /** * Validate Fetch API properties work as expect. */ var createFetchAPITest = function(value, evaluate) { var name = 'Fetch API - ' + value; var test = createFunctionalTest(name, 'Fetch API'); test.prototype.title = 'Test if ' + value + ' in Fetch API works.'; test.prototype.start = function(runner) { try { if (evaluate()) { return runner.succeed(); } throw Error(name + ' errors'); } catch (e) { return runner.fail(e); } } }; createFetchAPITest('fetch()', function() { return !!(window.fetch); }); createFetchAPITest('Headers', function() { var httpHeaders = { 'Content-Type' : 'image/jpeg', 'Accept-Charset' : 'utf-8' }; var testHeaders = new Headers(httpHeaders); return testHeaders.get('Content-Type') == "image/jpeg"; }); /** * Validate Fetch API Request work as expect. */ var testFetchAPIRequest= createFunctionalTest( 'Fetch API - Request', 'Fetch API'); testFetchAPIRequest.prototype.title = 'Test if Fetch API Request works.'; testFetchAPIRequest.prototype.start = function(runner) { try { var fetchRequest = new Request( 'https://qual-e.appspot.com/test', {method: 'POST', body: '{"result":"pass"}'}); // This shouldn't work, as method is read-only fetchRequest.method = 'GET'; } catch (e) { if (fetchRequest.url == 'https://qual-e.appspot.com/test' && fetchRequest.method == 'POST' && fetchRequest.credentials != 'include') return runner.succeed(); } return runner.fail('Fetch API Request should be read-only.') }; /** * Validate Fetch API Response work as expect. */ var testFetchAPIResponse= createFunctionalTest( 'Fetch API - Response', 'Fetch API'); testFetchAPIResponse.prototype.title = 'Test if Fetch API Response works.'; testFetchAPIResponse.prototype.start = function(runner) { try { var init = {'status': 200, 'statusText': 'YouTube'}; var myResponse = new Response('test_body', init); myResponse.statusText = 'Should not work since it is read-only'; } catch (e) { if (myResponse.statusText == 'YouTube' && myResponse.ok) { return runner.succeed(); } } return runner.fail('Fetch API does not work as expected'); }; /** * Test fetch API's streaming feature. */ var testFetchStreamAPIRequest = createFunctionalTest( 'Fetch API - stream', 'Fetch API'); testFetchStreamAPIRequest.prototype.title = 'Test if Fetch API handles steaming. '; testFetchStreamAPIRequest.prototype.start = function(runner){ try { const FETCH_TIME_OUT = 3000; const timeout = setTimeout(function(){ throw Error('Fetch took too long'); }, FETCH_TIME_OUT); fetch('lib/functional/fetch-sample.txt').then(function(response) { var reader = response.body.getReader(); var partialCell = ''; var returnNextCell = false; var returnCellAfter = "YouTube"; function search() { return reader.read().then(function(result) { var chunk = result.value; for (var i = 3; i < chunk.byteLength; i++) { partialCell += String.fromCharCode(chunk[i]); } var cellBoundry = /(?:,|\r\n)/; var completeCells = partialCell.split(cellBoundry); if (!result.done) { // last cell may not be complete partialCell = completeCells[completeCells.length - 1]; completeCells = completeCells.slice(0, -1); } for (var cell of completeCells) { cell = cell.trim(); if (returnNextCell) { reader.cancel("No more reading needed."); return cell; } if (cell === returnCellAfter) { returnNextCell = true; } } if (result.done) { throw Error("Could not find value after " + returnCellAfter); } return search(); }); } return search(); }).then(function(){ clearTimeout(timeout); return runner.succeed(); }); } catch (e) { return runner.fail(e); } }; /** * Validate Streams API - ReadableByteStream. */ var testStreamsAPI= createFunctionalTest( 'Streams API - ReadableByteStream', 'Assorted'); testStreamsAPI.prototype.title = 'Test Streams API - ReadableByteStream.'; testStreamsAPI.prototype.start = function(runner) { try { var init = {'status': 200, 'statusText' : 'YouTube'}; var myResponse = new Response('test_body', init); if (!!window.ReadableStream && myResponse.ok) { return runner.succeed(); } throw Error('Response not ok'); } catch (e) { return runner.fail(e); } }; /** * Validate browser supports strict mode in ECMA262-5. */ var testECMA262= createFunctionalTest('ECMA262-5 Strict Mode', 'Assorted'); testECMA262.prototype.title = 'Test browser supports strict mode in ECMA262-5.'; testECMA262.prototype.start = function(runner) { try { 'use strict'; var a = 1; return runner.succeed(); } catch (e) { return runner.fail(e) } }; /** * Validate the existence of window.requestAnimationFrame through Modernizr. */ var testAnimationFrame= createFunctionalTest( 'RequestAnimationFrame', 'Assorted'); testAnimationFrame.prototype.title = 'Test request Animation Frame.'; testAnimationFrame.prototype.start = function(runner) { try { if (Modernizr.prefixed('requestAnimationFrame', window, false)) { return runner.succeed(); } throw Error('window.requestAnimationFrame does not exist'); } catch (e) { return runner.fail(e) } }; /** * Test if js date object is correctly supported. */ var testJSDateObject= createFunctionalTest( 'JavaScript Date Object', 'Assorted'); testJSDateObject.prototype.title = 'Test if js date object is supported.'; testJSDateObject.prototype.start = function(runner) { try { var dateObj = new Date(Date.parse('2012-11-01T14:12:09.000Z')); var dateObjFormatted = dateObj.getUTCDate() + '/' + (dateObj.getUTCMonth() + 1) + '/' + dateObj.getUTCFullYear().toString().substr(2,2); if (dateObjFormatted == '1/11/12' || dateObjFormatted == '2012/11/2') { return runner.succeed(); } throw Error('date object is not supported correctly'); } catch (e) { return runner.fail(e) } }; /** * Validate support for specified image format. */ var createImgTest = function(suffix) { var test = createFunctionalTest(suffix.toUpperCase(), 'Assorted'); test.prototype.title = 'Test image format ' + suffix; test.prototype.start = function(runner) { try { var imgElement = createElement('img'); var url = TEST_MEDIA_SRC[suffix]; imgElement.setAttribute('src', url); testArea.appendChild(imgElement); imgElement.onload = function() { testArea.removeChild(imgElement); imgElement = null; return runner.succeed(); }.bind(this); } catch (e) { return runner.fail(e); } } }; createImgTest('Jpg'); createImgTest('Png'); /** * Validate the window height and width are in right size. */ var testWindowSize = createFunctionalTest('Window Size', 'Assorted'); testWindowSize.prototype.title = 'Test window size in current browser.'; testWindowSize.prototype.start = function(runner) { try { var result = false; switch (window.innerHeight) { case 720: if(window.innerWidth == 1280) result = true; break; case 1080: if(window.innerWidth == 1920) result = true; break; case 1440: if(window.innerWidth == 2560) result = true; break; case 2160: if(window.innerWidth == 3840) result = true; break; } if (!result) { throw 'Failed test with window size in ' + window.innerWidth + ' x ' + window.innerHeight; } return runner.succeed(); } catch (e) { return runner.fail(e); } }; /** * Validate existence of WebP properties. */ var createWebPTest = function(name, uri) { var test = createFunctionalTest(name, 'WebP'); test.prototype.title = 'Test ' + name + ' support.'; test.prototype.start = function(runner) { var img = new Image(); img.addEventListener('load', function() { return runner.succeed(); }, false); img.addEventListener('error', function(event) { return runner.fail('Request to website with valid SSL certificate fails.'); }, false); img.src = uri; }; }; var webpTests = [{ 'uri': 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA=', 'name': 'WebP' }, { 'uri': 'data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAABBxAR/Q9ERP8DAABWUDggGAAAADABAJ0BKgEAAQADADQlpAADcAD++/1QAA==', 'name': 'WebP Alpha' }, { 'uri': 'data:image/webp;base64,UklGRlIAAABXRUJQVlA4WAoAAAASAAAAAAAAAAAAQU5JTQYAAAD/////AABBTk1GJgAAAAAAAAAAAAAAAAAAAGQAAABWUDhMDQAAAC8AAAAQBxAREYiI/gcA', 'name': 'WebP Animation' }, { 'uri': 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=', 'name': 'WebP Lossles' }]; for (var i = 0; i < webpTests.length; i++) { createWebPTest(webpTests[i].name, webpTests[i].uri); } /** * Validate specified video format could be played. */ var createMediaFormatTest = function(name, stream, codec, mandatory) { var test = createFunctionalTest(name, 'Video / Audio', mandatory); test.prototype.title = 'Test if browser can play ' + name; test.prototype.start = function(runner, video) { try { if (!!(MediaSource.isTypeSupported && MediaSource.isTypeSupported( stream + '; codecs="' + codec + '"'))) { return runner.succeed(); } throw name + ' not supported.'; } catch (e) { return runner.fail(e); } }; }; createMediaFormatTest('MP4 + H.264', 'video/mp4', 'avc1.4d401e'); createMediaFormatTest('WebM + VP9', 'video/webm', 'vp9'); createMediaFormatTest('WebM + VP9 Profile 2', 'video/webm', 'vp9.2'); createMediaFormatTest('WebM + Opus', 'audio/webm', 'opus', false); /** * Ensure WebGL is correctly supported. */ var testWebGLSupport = createFunctionalTest( 'WebGL Support Detected', 'WebGL', harnessConfig.support_webgl); testWebGLSupport.prototype.title = 'Test if WebGL is correctly supported'; testWebGLSupport.prototype.start = function(runner) { try { if (window.WebGLRenderingContext) { var canvas = createElement('canvas'); var ctx = canvas.getContext('webgl'); if (!!(ctx)) { return runner.succeed(); } } throw 'WebGL not correctly supported.' } catch (e) { return runner.fail(e); } }; /** * Ensure WebGL is properly disabled if not supported. */ var testWebGLDisabled = createFunctionalTest( 'WebGL Properly Disabled', 'WebGL', !harnessConfig.support_webgl); testWebGLDisabled.prototype.title = 'Test if WebGL is properly disabled'; testWebGLDisabled.prototype.start = function(runner) { try { if (window.WebGLRenderingContext) { var canvas = createElement('canvas'); var ctx = canvas.getContext('webgl'); if (ctx) { throw 'WebGL not correctly disbabled.' } } return runner.succeed(); } catch (e) { return runner.fail(e); } }; /** * Test WebSpeech API through Modernizr. */ var testWebSpeechAPI = createFunctionalTest( 'WebSpeech API', 'Assorted', harnessConfig.support_webspeech); testWebSpeechAPI.prototype.title = 'Test WebSpeech API.'; testWebSpeechAPI.prototype.start = function(runner) { try { if (Modernizr.speechrecognition) { return runner.succeed(); } throw 'WebSpeech not supported.'; } catch (e) { return runner.fail(e); } }; /** * Ensure video/x-flv is unsupported. */ var testNoVideoXFlv = createFunctionalTest( 'video/x-flv is unsupported', 'Assorted'); testNoVideoXFlv.prototype.title = 'Make sure video/x-flv is unsupported.'; testNoVideoXFlv.prototype.start = function(runner) { try { if (!Modernizr.video.flv) { return runner.succeed(); } throw 'video/x-flv is still supported.' } catch (e) { return runner.fail(e); } }; /** * Validate MediaSource properties through Modernizr. */ var createMediaSourceTest = function(arg) { var test = createFunctionalTest('MediaSource.' + arg, 'MediaSource'); test.prototype.title = 'Test if MediaSource.' + arg + ' is supported.'; test.prototype.start = function(runner, video) { try { var media = new MediaSource(); window.attachMediaSource(video, media); if (Modernizr.prefixed(arg, media, false) != false) { return runner.succeed(); } throw 'MediaSource.' + arg + ' support failed.'; } catch (e) { return runner.fail(e); } }; } createMediaSourceTest('addSourceBuffer'); createMediaSourceTest('sourceBuffers'); createMediaSourceTest('activeSourceBuffers'); createMediaSourceTest('duration'); createMediaSourceTest('removeSourceBuffer'); createMediaSourceTest('readyState'); createMediaSourceTest('endOfStream'); /** * Test if specified type is supported through videoElement.isTypeSupported. */ var createEMETest = function(name, evaluate, mandatory) { var test = createFunctionalTest(name, 'EME Basic', mandatory); test.prototype.title = 'Test ' + name; test.prototype.start = function(runner, video) { try { if (evaluate(video)) { return runner.succeed(); } throw name + ' failed.'; } catch (e) { return runner.fail(e); } } }; createEMETest( 'canPlayType.keySystem', function(videoElement) { return videoElement.canPlayType( util.createVideoFormatStr('mp4', 'avc1.42E01E'), 'org.w3.clearkey') != undefined || videoElement.canPlayType( util.createVideoFormatStr('webm', 'vp9'), 'org.w3.clearkey') != undefined; }); var widevineTest = function(videoElement) { return videoElement.canPlayType( util.createVideoFormatStr('mp4', 'avc1.42E01E'), 'com.widevine.alpha') != undefined || videoElement.canPlayType( util.createVideoFormatStr('webm', 'vp9'), 'com.widevine.alpha') != undefined; }; createEMETest('Widevine', widevineTest); var playreadyTest = function(videoElement) { return videoElement.canPlayType( util.createVideoFormatStr('mp4', 'avc1.42E01E'), 'com.youtube.playready') != undefined; }; createEMETest('PlayReady', playreadyTest, false); createEMETest('DRM', function(videoElement) { return widevineTest || playreadyTest; }); createEMETest( 'Video.generateKeyRequest', function(videoElement) { return Modernizr.prefixed( 'generateKeyRequest', videoElement, false) != false; }, false); /** * Creates a MediaDevices test that validates whether the device has * Audio input media device and it implements correctly. */ var createMediaDevicesTest = function() { var name = 'EnumerateDevices'; var test = createFunctionalTest(name, name, harnessConfig.support_webspeech); test.prototype.title = 'Test if ' + name + ' exists'; test.prototype.start = function(runner) { try { if (undefined == navigator.mediaDevices.enumerateDevices) { throw Error('enumerateDevices does not exists!'); } navigator.mediaDevices.enumerateDevices().then(function(listOfDevices) { for (var device of listOfDevices) { if (undefined == device.kind) { throw Error('kind does not exists!'); } if (undefined == device.label) { throw Error('label does not exists!'); } if (device.kind == "audioinput") { return runner.succeed(); } } throw Error('No audioinput found!'); }); } catch (e) { return runner.fail(e); } }; }; createMediaDevicesTest(); /** * Create a UAString test that validates the format of current User Agent. */ var createUAStringTest = function() { var name = 'User Agent'; var test = createFunctionalTest(name, name); test.prototype.title = 'Test if ' + name + ' format is correct'; test.prototype.start = function(runner) { try{ var re_2019 = new RegExp( [ '([-.A-Za-z0-9\\\\\\/]*)_([-.A-Za-z0-9\\\\\\/]*)_([-.A-Za-z0-9\\', '\\\\/]*)_2019 ?\\/ ?[-_.A-Za-z0-9\\\\]* \\(([-_.A', '-Za-z0-9\\\\\\/ ]+), ?([^,]*), ?([WIREDLSwiredls\\\\\\/]*)\\)' ].join('')); var useragentParsed = re_2019.exec(navigator.userAgent); if (useragentParsed && useragentParsed[2].match(/^(BDP|GAME|OTT|STB|TV|ATV)$/)) { return runner.succeed(); } throw Error('invalid User agent format'); } catch (e) { return runner.fail(e); } }; }; createUAStringTest(); /** * Create a UAString test that validates version of certain items in user agent. */ var createCobaltVersionTest = function(regexp, version, name) { var title = 'User Agent'; var test = createFunctionalTest(name + ' ' + title, title); test.prototype.title = 'Test if ' + name + title + ' format is correct'; test.prototype.start = function(runner) { try { var useragentParsed = regexp.exec(navigator.userAgent); if (name === 'Cobalt') { var xhr = new XMLHttpRequest(); xhr.open('GET', COBALT_REQ_URL); xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { if (xhr.status === 200) { try { var buildVersion = Number(window.atob(xhr.responseText)); } catch (e) { runner.fail(e); } if (useragentParsed && Number(useragentParsed[1].split('.')[1]) === buildVersion) { return runner.succeed(); } runner.fail(Error(name + ' version is not ' + buildVersion)); } else { runner.fail(Error('Failed to request Cobalt build version')); } } }; } else { if (useragentParsed && Number(useragentParsed[1]) >= version) { return runner.succeed(); } throw Error(name + ' version is less than ' + version); } } catch (e) { return runner.fail(e); } }; }; createCobaltVersionTest(new RegExp(/19\.lts\.([0-9]\.[0-9]{6})/), 0, 'Cobalt'); createCobaltVersionTest(new RegExp(/Starboard\/([0-9]+)/), 10, 'Starboard'); /** * Create a CPU memory test that validates the size of memory allocation. */ var createMemoryTest = function(processor, size) { var title = processor + ' Memory Allocation'; var test = createFunctionalTest(title, title); test.prototype.title = 'Test if ' + title + ' is correct'; test.prototype.start = function(runner) { try{ if (typeof h5vcc != 'undefined' && typeof h5vcc.cVal != 'undefined') { var memFree = Number(h5vcc.cVal.getValue('Memory.' + processor + '.Free')); var memUsed = Number(h5vcc.cVal.getValue('Memory.' + processor + '.Used')); if (memFree + memUsed < size) { throw Error(processor + ' memory allocated less than ' + size); } } else { var a = new Array(size - 50 * 1024 * 1024).join('M'); var f = function(arr){}; f(a); } return runner.succeed(); } catch (e) { return runner.fail(e); } }; }; createMemoryTest('CPU', 150 * 1024 * 1024); /** * Ensure Same-origin Policy is enabled through accessing localStorage. */ var testSameOriginPolicy = createFunctionalTest( 'Same-origin Policy', 'Assorted'); testSameOriginPolicy.prototype.title = 'Test Same-origin Policy.'; testSameOriginPolicy.prototype.start = function(runner) { var value = localStorage.getItem('yt.leanback::schema-version'); runner.log('[Same-origin Policy] - localStorage.getItem(' + '"yt.leanback::schema-version"); returned: ' + value); runner.checkEq( value, null, 'localStorage.getItem("yt.leanback::schema-version")'); return runner.succeed(); }; var createFontDiv = function(format, content) { var span = createElement('span'); span.className = 'dancing-script-' + format; span.innerHTML = content; return span; }; /** * Validate specified font format is supported by browser. * Assume ttf is supported. */ var createFontTest = function(format) { var test = createFunctionalTest('Fonts - ' + format, 'Fonts', false); test.prototype.title = 'Test if font format ' + format + ' is supported.'; test.prototype.start = function(runner) { try { testArea.removeAttribute('style'); var fontDiv = createElement('div'); fontDiv.className = 'font-block'; testArea.appendChild(fontDiv); var characters = 'ABCĆČDĐEFGHIJKLMNOPQRSŠTUVWXYZŽabcčćdđefghijklmnopqrsštuvwxyzž' + 'ĂÂÊÔƠƯăâêôơư1234567890‘?’“!”(%)[#]{@}/&amp;\&lt;-+÷×=&gt;®©$€£¥¢:;,.*'; var ttfEle = createFontDiv('ttf', characters) var testEle = createFontDiv(format, characters) fontDiv.appendChild(ttfEle); fontDiv.appendChild(testEle); // Cobalt does not emit any event when fonts load finish. // So use setTimeout as a workaround. setTimeout(function(event) { var ttfWidth = ttfEle.offsetWidth; var testWidth = testEle.offsetWidth; testArea.removeChild(fontDiv); testArea.setAttribute('style', 'display: none'); if (ttfWidth !== testWidth) { throw 'Widths don\'t match: ttf:' + ttfWidth + ' and ' + format + ':' + testWidth; } return runner.succeed(); }, MS_TO_WAIT_FOR_FONTS); } catch (e) { return runner.fail(e); } }; }; createFontTest('woff'); createFontTest('woff2'); var setCookie = function(key, value, path, domain, expires, httponly) { var payload = key + '=' + value; payload += '; path=' + (path ? path : '/'); payload += '; domain=' + (domain ? domain : '.ytlr-cert.appspot.com'); if (expires) { payload += '; expires=' + expires; } if (httponly) { payload += '; httponly'; } document.cookie = payload; } var setCookieObj = function(cookie) { setCookie( cookie['key'], cookie['value'], cookie['path'], cookie['domain'], cookie['expires'], cookie['httponly']); }; var getCookieObject = function() { var arr = document.cookie.split('; '); var result = {}; for (var i = 0; i < arr.length; i++) { var kv = arr[i].split('='); if (kv.length > 1) { result[kv[0]] = kv[1]; } } return result; }; // Clear cookie by marking it as expired. var clearCookie = function(cookie) { setCookie( cookie['key'], cookie['value'], cookie['path'], cookie['domain'], 'Thu, 01-Jan-1970 00:00:00 GMT', cookie['httponly']); }; var clearTestCookies = function() { for (var i in valid_cookies) { clearCookie(valid_cookies[i]); } for (var i in invalid_cookies) { clearCookie(invalid_cookies[i]); } }; var getCookiePath = function() { return (testVersion != 'Test-In-Progress') ? testVersion : 'tip'; }; // 'valid_path_cookie' and 'valid_domain_cookie' would fail if you download // the source code and setup your own server var valid_cookies = [ {key: 'valid_cookie', value: 'valid'}, {key: 'valid_path_cookie', value: 'valid', path: getCookiePath()}, { key: 'valid_domain_cookie', value: 'valid', domain: '.ytlr-cert.appspot.com' } ]; var invalid_cookies = [ {key: 'invalid_http_only', value: 'invalid', httponly: true}, {key: 'invalid_path_cookie', value: 'invalid', path: '/p/a/t/h'}, { key: 'invalid_domain_cookie', value: 'invalid', domain: '.invalid-domain.com' }, { key: 'invalid_expired_cookie', value: 'invalid', expires: 'Thu, 01-Jan-1970 00:00:00 GMT' } ]; /** * Ensure browser could set cookie and save properly. */ var testSetCookie = createFunctionalTest('Set Cookie', 'Cookie'); testSetCookie.prototype.title = 'Test if browser could save cookie.' testSetCookie.prototype.start = function(runner) { try { for (var i in valid_cookies) { setCookieObj(valid_cookies[i]); } for (var i in invalid_cookies) { setCookieObj(invalid_cookies[i]); } var keys = Object.keys(getCookieObject()); for (var i in valid_cookies) { var k = valid_cookies[i].key; if (!keys.includes(k)) { throw 'Key ' + k + ' should exist in document.cookie but it does not.'; } } for (var i in invalid_cookies) { var k = invalid_cookies[i].key; if (keys.includes(k)) { throw 'Key ' + k + ' should not exist in document.cookie but it does.'; } } return runner.succeed(); } catch (e) { return runner.fail(e); } finally { clearTestCookies(); } }; /** * Ensure browser could set cookie and save properly. */ var testSetExpiredCookie = createFunctionalTest('Set Expired Cookie', 'Cookie'); testSetExpiredCookie.prototype.title = 'Test if browser ignores expired cookie.' testSetExpiredCookie.prototype.start = function(runner) { try { for (var i in valid_cookies) { setCookieObj(valid_cookies[i]); } var keys = Object.keys(getCookieObject()); for (var i in valid_cookies) { var k = valid_cookies[i].key; if (!keys.includes(k)) { throw 'Key ' + k + ' should exist in document.cookie but it does not.'; } } for (var i in valid_cookies) { clearCookie(valid_cookies[i]); } keys = Object.keys(getCookieObject()); for (var i in valid_cookies) { var k = valid_cookies[i].key; if (keys.includes(k)) { throw 'Key ' + k + ' should not exist in document.cookie but it does.'; } } return runner.succeed(); } catch (e) { return runner.fail(e); } finally { clearTestCookies(); } }; /** * Validate certain number of cookies is supported by browser. */ var createCookieStorageTest = function(name, count) { var test = createFunctionalTest(name, 'Cookie'); test.prototype.title = 'Test if browser supports ' + count + '4kb cookies.'; test.prototype.start = function(runner) { var cookie_size = 4000; try { var cookie_storage = document.cookie.length; for (var i = 0; i < count; i++) { var prefix = 'CookieStorageTest' + ('000' + i).substr(-3) + '='; var payload = prefix + new Array(cookie_size - prefix.length + 1).join('m'); document.cookie = payload; if (document.cookie.length - cookie_storage < cookie_size) throw 'Fail to add the No.' + i + ' cookie'; else cookie_storage = document.cookie.length; } return runner.succeed(); } catch (e) { return runner.fail(e); } finally { document.cookie.split(';').forEach(function(c) { if (c.trim().startsWith('CookieStorageTest')) { document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString()); } }); } }; }; createCookieStorageTest('Min Cookie Storage', 50); createCookieStorageTest('Max Cookie Storage', 150); /** * Validate browser could correctly handle request from server with valid and * invalid certificates. */ var createSslTest = function(name, origin, shouldFail = false) { var test = createFunctionalTest(name, 'SSL'); test.prototype.title = 'Test if browser correctly handle to request to ' + origin; test.prototype.start = function(runner) { var url = origin + 'test/dashboard/small-image.png'; var img = new Image(); img.addEventListener('load', function() { if (shouldFail) { return runner.fail( 'Request to website with invalid SSL certificate succeeds.' + 'Invalid SSL requests must not be allowed to load.'); } return runner.succeed(); }, false); img.addEventListener('error', function(event) { if (shouldFail) { return runner.succeed(); } return runner.fail('Request to website with valid SSL certificate fails.'); }, false); img.src = url; }; }; createSslTest('Self-Signed', 'https://self-signed.badssl.com/', true); createSslTest('expired', 'https://expired.badssl.com/', true); createSslTest('sha256', 'https://sha256.badssl.com/'); createSslTest('TLS', 'https://tls-v1-2.badssl.com:1012/'); var testGlobalSignR2 = createFunctionalTest('GlobalSign RootCA R2', 'SSL'); testGlobalSignR2.prototype.title = 'Test if browser correctly handle to' + 'request with GlobalSign RootCA R2 certificate.'; testGlobalSignR2.prototype.start = function(runner) { try { var xhr = new XMLHttpRequest(); xhr.open('GET', 'https://cert-test.sandbox.google.com/', true); xhr.send(); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if (xhr.status != 200) { runner.fail(Error('Xhr request status is not 200 ok')); } if (!xhr.responseText.includes('Client test successful.')) { runner.fail( Error('XHR Response not as expected: ' + xhr.responseText)); } return runner.succeed(); } }; } catch (e) { return runner.fail(e); } }; var testGlobalSignR3 = createFunctionalTest('GlobalSign RootCA R3', 'SSL'); testGlobalSignR3.prototype.title = 'Test if browser correctly handle to' + 'request with GlobalSign RootCA R3 certificate.'; testGlobalSignR3.prototype.start = function(runner) { try { var url = "https://www.globalsign.com/files/4114/9494/4842/switch-to-secure-ssl-globalsign.jpg" var img = new Image(); img.addEventListener('load', function() { return runner.succeed(); }, false); img.addEventListener('error', function(event) { runner.fail(Error('Request to website with valid SSL certificate fails.')); }, false); img.src = url; } catch (e) { return runner.fail(e); } }; return {tests: tests, info: info, fields: fields, viewType: 'default'}; };