mirror of
https://github.com/shaka-project/shaka-player.git
synced 2026-06-13 15:46:46 +03:00
test: Fix Chromecast testing in lab (#6643)
These changes are necessary for compatibility with Chromecast WebDriver Server v2. - Fix a bug in Karma's flat environment support (joeyparrish/karma@9875e98) - Add a test boot file to load CAF on Chromecast devices; required by Chromecast WebDriver Server v2's redirect mode (flat environment at that level) - Also load this cast-boot file in support.html - Rename/reorganize Cast-related externs, which were messy even before the addition of CAF - Remove proxy-cast-platform.js; no longer needed as we move to flatten the test environment - Ignore error events with "null" error; these appear on Linux Chromecasts, only since including CAF - Ignore error events that are actually strings; these appear on Linux Chromecasts, only since including CAF - Disable Fuchsia in the lab until autoplay issues can be resolved
This commit is contained in:
+9
-2
@@ -45,6 +45,7 @@ import argparse
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
|
||||
import compiler
|
||||
import generateLocalizations
|
||||
@@ -302,11 +303,12 @@ class Build(object):
|
||||
if not closure.compile(closure_opts, force):
|
||||
return False
|
||||
|
||||
source_base = shakaBuildHelpers.get_source_base()
|
||||
|
||||
# Don't pass local node modules to the extern generator. But don't simply
|
||||
# exclude the string 'node_modules', either, since Shaka Player could be
|
||||
# rebuilt after installing it as a node module.
|
||||
node_modules_path = os.path.join(
|
||||
shakaBuildHelpers.get_source_base(), 'node_modules')
|
||||
node_modules_path = os.path.join(source_base, 'node_modules')
|
||||
local_include = set([f for f in self.include if node_modules_path not in f])
|
||||
extern_generator = compiler.ExternGenerator(local_include, build_name)
|
||||
|
||||
@@ -323,6 +325,11 @@ class Build(object):
|
||||
if not ts_def_generator.generate(force):
|
||||
return False
|
||||
|
||||
# Copy this file to dist/ where support.html can use it
|
||||
shutil.copy(
|
||||
os.path.join(source_base, 'test', 'test', 'cast-boot.js'),
|
||||
os.path.join(source_base, 'dist', 'cast-boot.js'))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -283,7 +283,6 @@ def check_tests(args):
|
||||
[closure_base_js]))
|
||||
files.add(os.path.join(base, 'demo', 'common', 'asset.js'))
|
||||
files.add(os.path.join(base, 'demo', 'common', 'assets.js'))
|
||||
files.add(os.path.join(base, 'proxy-cast-platform.js'))
|
||||
|
||||
localizations = compiler.GenerateLocalizations(None)
|
||||
localizations.generate(args.force)
|
||||
|
||||
@@ -73,7 +73,6 @@ requirement {
|
||||
whitelist_regexp: "demo/"
|
||||
whitelist_regexp: "test/"
|
||||
whitelist_regexp: "node_modules/"
|
||||
whitelist_regexp: "proxy-cast-platform.js"
|
||||
|
||||
# This global variable is generated by Google-internal tooling, and should be
|
||||
# allowed. It will not end up in the compiled code, only at an intermediate
|
||||
@@ -287,7 +286,6 @@ requirement: {
|
||||
"shaka.util.Timer instead."
|
||||
whitelist_regexp: "demo/"
|
||||
whitelist_regexp: "test/"
|
||||
whitelist_regexp: "proxy-cast-platform.js"
|
||||
}
|
||||
|
||||
# Disallow eval, except when testing for modern JS syntax in demo
|
||||
|
||||
@@ -241,6 +241,8 @@ ChromecastGTV:
|
||||
ChromecastHub:
|
||||
browser: chromecast
|
||||
version: hub
|
||||
# Disabled by default until we resolve issues with autoplay on Fuchsia.
|
||||
disabled: true
|
||||
|
||||
ChromecastSpeaker:
|
||||
# This is a headless device, and our tests are not yet known to work here.
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2024 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Google Cast namespace definitions, shared by other externs
|
||||
* files.
|
||||
* @externs
|
||||
*/
|
||||
|
||||
/** @const */
|
||||
var cast = {};
|
||||
@@ -15,10 +15,6 @@
|
||||
var __onGCastApiAvailable;
|
||||
|
||||
|
||||
/** @const */
|
||||
var cast = {};
|
||||
|
||||
|
||||
/** @const */
|
||||
cast.receiver = {};
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2024 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Externs for the limited subset of the Cast Application
|
||||
* Framework (Receiver SDK v3) that we use in our test infrastructure.
|
||||
*
|
||||
* @externs
|
||||
*/
|
||||
|
||||
/** @const */
|
||||
cast.framework = {};
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* statusText: string,
|
||||
* disableIdleTimeout: boolean,
|
||||
* skipPlayersLoad: boolean
|
||||
* }}
|
||||
*/
|
||||
cast.framework.CastReceiverOptions;
|
||||
|
||||
cast.framework.CastReceiverContext = class {
|
||||
/** @return {!cast.framework.CastReceiverContext} */
|
||||
static getInstance() {}
|
||||
|
||||
/**
|
||||
* @param {!cast.framework.CastReceiverOptions} options
|
||||
* @return {!cast.framework.CastReceiverContext}
|
||||
*/
|
||||
start(options) {}
|
||||
};
|
||||
Executable → Regular
@@ -5,8 +5,8 @@
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Externs for HTMLMediaElement related to casting which were
|
||||
* missing in the Closure compiler.
|
||||
* @fileoverview Externs for HTMLMediaElement related to remote playback which
|
||||
* were missing in the Closure compiler.
|
||||
*
|
||||
* @externs
|
||||
*/
|
||||
+7
-5
@@ -197,7 +197,13 @@ module.exports = (config) => {
|
||||
|
||||
// list of files / patterns to load in the browser
|
||||
files: [
|
||||
// Polyfills first, primarily for IE 11 and older TVs:
|
||||
// The Cast boot file must come first, to start the SDK and respond as
|
||||
// quickly as possible to the Cast platform. Without this up front, we
|
||||
// tend to see the Chromecast time out and shut down the receiver that
|
||||
// hosts our tests.
|
||||
'test/test/cast-boot.js',
|
||||
|
||||
// Polyfills before anything else, primarily for older TVs:
|
||||
// Promise polyfill, required since we test uncompiled code on IE11
|
||||
'node_modules/es6-promise-polyfill/promise.js',
|
||||
// Babel polyfill, required for async/await
|
||||
@@ -230,9 +236,6 @@ module.exports = (config) => {
|
||||
// test utilities next, which fill in that namespace
|
||||
'test/test/util/*.js',
|
||||
|
||||
// Proxy cast.__platform__ methods across frames, necessary in testing
|
||||
'proxy-cast-platform.js',
|
||||
|
||||
// bootstrapping for the test suite last; this will load the actual tests
|
||||
'test/test/boot.js',
|
||||
|
||||
@@ -389,7 +392,6 @@ module.exports = (config) => {
|
||||
'ui/**/*.js': ['babel', 'sourcemap'],
|
||||
'test/**/*.js': ['babel', 'sourcemap'],
|
||||
'third_party/**/*.js': ['babel', 'sourcemap'],
|
||||
'proxy-cast-platform.js': ['babel', 'sourcemap'],
|
||||
},
|
||||
|
||||
babelPreprocessor: {
|
||||
|
||||
Generated
+2
-2
@@ -5537,7 +5537,7 @@
|
||||
},
|
||||
"node_modules/karma": {
|
||||
"version": "6.4.3",
|
||||
"resolved": "git+ssh://git@github.com/joeyparrish/karma.git#d98765e43fe801b35452fdb6cab3d1d0bac519a2",
|
||||
"resolved": "git+ssh://git@github.com/joeyparrish/karma.git#9875e9898b999684c2b2767c34d766ab2220f351",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -12947,7 +12947,7 @@
|
||||
}
|
||||
},
|
||||
"karma": {
|
||||
"version": "git+ssh://git@github.com/joeyparrish/karma.git#d98765e43fe801b35452fdb6cab3d1d0bac519a2",
|
||||
"version": "git+ssh://git@github.com/joeyparrish/karma.git#9875e9898b999684c2b2767c34d766ab2220f351",
|
||||
"dev": true,
|
||||
"from": "karma@github:joeyparrish/karma#shaka-fixes",
|
||||
"requires": {
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2016 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Proxy cast platform methods across frames. Shared between the
|
||||
* test environment and support.html.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Patch Cast's cast.__platform__.canDisplayType to allow it to operate across
|
||||
* frames and origins in our testing environment.
|
||||
*
|
||||
* The Cast runtime only exposes cast.__platform__ on the top window, not
|
||||
* iframes embedded within it. However, both Chromecast WebDriver Server in
|
||||
* our lab and the test runner Karma use iframes, and there are three different
|
||||
* origins involved: WebDriver Server's receiver at github.io, Karma's
|
||||
* top-level, and an inner frame of Karma that is raw HTML as text with no
|
||||
* origin.
|
||||
*
|
||||
* With all of these complexities, the only way to access cast.__platform__ is
|
||||
* asynchronously via postMessage. This means all callers of
|
||||
* cast.__platform__.canDisplayType must use `await`, even though the
|
||||
* underlying method is synchronous. If the caller is running in the top frame
|
||||
* (as in a real receiver), `await` will do no harm. If the caller is running
|
||||
* inside our tests in Karma, the `await` is critical to access __platform__
|
||||
* via this shim.
|
||||
*/
|
||||
function proxyCastCanDisplayType() {
|
||||
if (!navigator.userAgent.includes('CrKey')) {
|
||||
// Not Chromecast, do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the namespaces if needed.
|
||||
if (!window.cast) {
|
||||
window['cast'] = {};
|
||||
}
|
||||
if (!cast.__platform__) {
|
||||
cast['__platform__'] = {};
|
||||
}
|
||||
|
||||
if (cast.__platform__.canDisplayType) {
|
||||
// Already exists, do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an async shim. Calls to canDisplayType will be translated into
|
||||
// async messages to the top frame, which will then execute the method and
|
||||
// post a message back with results (or an error). The resolve/reject
|
||||
// functions for the shim's returned Promise will be stored temporarily in
|
||||
// these maps and matched up by request ID.
|
||||
/** @type {!Map<number, function(?)>} */
|
||||
const resolveMap = new Map();
|
||||
/** @type {!Map<number, function(?)>} */
|
||||
const rejectMap = new Map();
|
||||
/** @type {number} */
|
||||
let nextId = 0;
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* id: number,
|
||||
* type: string,
|
||||
* result: *,
|
||||
* }}
|
||||
*/
|
||||
let CastShimMessage;
|
||||
|
||||
// Listen for message events for results/errors from the top frame.
|
||||
window.addEventListener('message', (event) => {
|
||||
const data = /** @type {CastShimMessage} */(event['data']);
|
||||
console.log('Received cross-frame message', data);
|
||||
|
||||
if (data.type == 'cast.__platform__:result') {
|
||||
// Find the matching resolve function and resolve the promise for this
|
||||
// request.
|
||||
const resolve = resolveMap.get(data.id);
|
||||
if (resolve) {
|
||||
resolve(data.result);
|
||||
|
||||
// Clear both resolve and reject from the maps for this ID.
|
||||
resolveMap.delete(data.id);
|
||||
rejectMap.delete(data.id);
|
||||
}
|
||||
} else if (data.type == 'cast.__platform__:error') {
|
||||
// Find the matching reject function and reject the promise for this
|
||||
// request.
|
||||
const reject = rejectMap.get(data.id);
|
||||
if (reject) {
|
||||
reject(data.result);
|
||||
|
||||
// Clear both resolve and reject from the maps for this ID.
|
||||
resolveMap.delete(data.id);
|
||||
rejectMap.delete(data.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Shim canDisplayType to proxy the request up to the top frame.
|
||||
cast.__platform__.canDisplayType = /** @type {?} */(castCanDisplayTypeShim);
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @return {!Promise<boolean>}
|
||||
*/
|
||||
function castCanDisplayTypeShim(type) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Craft a message for the top frame to execute this method for us.
|
||||
const message = {
|
||||
id: nextId++,
|
||||
type: 'cast.__platform__',
|
||||
command: 'canDisplayType',
|
||||
args: Array.from(arguments),
|
||||
};
|
||||
|
||||
// Store the resolve and reject functions so we can act on results/errors
|
||||
// later.
|
||||
resolveMap.set(message.id, resolve);
|
||||
rejectMap.set(message.id, reject);
|
||||
|
||||
// Reject after a 5s timeout. This can happen if we're running under an
|
||||
// incompatible version of Chromecast WebDriver Server's receiver app.
|
||||
setTimeout(() => {
|
||||
reject(new Error('canDisplayType timeout!'));
|
||||
|
||||
// Clear both resolve and reject from the maps for this ID.
|
||||
resolveMap.delete(message.id);
|
||||
rejectMap.delete(message.id);
|
||||
}, 5000);
|
||||
|
||||
// Send the message to the top frame.
|
||||
console.log('Sending cross-frame message', message);
|
||||
window.top.postMessage(message, '*');
|
||||
});
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -38,9 +38,10 @@
|
||||
}
|
||||
|
||||
</style>
|
||||
<script src="dist/cast-boot.js"></script>
|
||||
<script src="dist/shaka-player.compiled.js"></script>
|
||||
<script src="proxy-cast-platform.js"></script>
|
||||
<script>
|
||||
|
||||
function whenLoaded(fn) {
|
||||
// IE 9 fires DOMContentLoaded, and enters the "interactive"
|
||||
// readyState, before document.body has been initialized, so wait
|
||||
@@ -74,7 +75,6 @@
|
||||
}
|
||||
|
||||
function doTest() {
|
||||
proxyCastCanDisplayType();
|
||||
shaka.polyfill.installAll();
|
||||
|
||||
if (shaka.Player.isBrowserSupported()) {
|
||||
|
||||
+24
-6
@@ -97,8 +97,29 @@ function failTestsOnUnhandledErrors() {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Window/error_event
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/ErrorEvent
|
||||
window.addEventListener('error', (event) => {
|
||||
if (typeof event == 'string') {
|
||||
// Since we moved to a flat (frameless) testing environment, we sometimes
|
||||
// get error "events" on Chromecast where we only get the string "Script
|
||||
// error." instead of an actual event object. This would be consistent
|
||||
// with an unhandled error thrown by a cross-origin script (such as the
|
||||
// Cast SDK), but this string-only form should only be sent to the
|
||||
// onerror callback and not the 'error' event listener. This is both a
|
||||
// bug in the SDK (unhandled error) and in the platform (called event
|
||||
// listener with wrong format). We ignore it here.
|
||||
console.log('Suppressing error event with only string:', event);
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {?} */
|
||||
const error = event['error'];
|
||||
if (!error) {
|
||||
// Since we moved to a flat (frameless) testing environment, we sometimes
|
||||
// get error events on Chromecast where the error field is null. It's not
|
||||
// clear why. Ignore these.
|
||||
console.log('Suppressing error event with no error:', event);
|
||||
return;
|
||||
}
|
||||
|
||||
failOnError('Unhandled error', error);
|
||||
});
|
||||
}
|
||||
@@ -129,9 +150,9 @@ function disableScrollbars() {
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
} catch (error) {
|
||||
// On some platforms (Chromecast, Tizen), we are prevented from accessing
|
||||
// the host context, even though it should be in the same origin. Ignore
|
||||
// errors here, so that the rest of the critical boot sequence can complete.
|
||||
// On some platforms (Tizen), we are prevented from accessing the host
|
||||
// context, even though it should be in the same origin. Ignore errors
|
||||
// here, so that the rest of the critical boot sequence can complete.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -414,9 +435,6 @@ function setupTestEnvironment() {
|
||||
disableScrollbars();
|
||||
workAroundLegacyEdgePromiseIssues();
|
||||
|
||||
// Defined in proxy-cast-platform.js:
|
||||
proxyCastCanDisplayType();
|
||||
|
||||
// The spec filter callback occurs before calls to beforeAll, so we need to
|
||||
// install polyfills here to ensure that browser support is correctly
|
||||
// detected.
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*! @license
|
||||
* Shaka Player
|
||||
* Copyright 2024 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// Use indexOf() here, normally banned in favor of includes(), for
|
||||
// compatibility with the oldest devices and runtimes.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
if (navigator.userAgent.indexOf('CrKey') != -1) {
|
||||
// Running in any frame of a Chromecast device.
|
||||
// Load and activate the Cast SDK.
|
||||
console.log('Loading Cast SDK');
|
||||
|
||||
const script =
|
||||
/** @type {HTMLScriptElement} **/(document.createElement('script'));
|
||||
|
||||
script.src = 'https://www.gstatic.com/cast/sdk/libs/caf_receiver/v3/cast_receiver_framework.js';
|
||||
|
||||
// Use an anonymous function here, normally banned in favor of arrow
|
||||
// functions, for compatibility with the oldest devices and runtimes.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
script.onload = function() {
|
||||
cast.framework.CastReceiverContext.getInstance().start({
|
||||
// What to show as the status of the app to senders
|
||||
statusText: 'Shaka Player Testing',
|
||||
// Don't shut down for a lack of sender input
|
||||
disableIdleTimeout: true,
|
||||
// Don't load player libraries, since we are loading Shaka here directly
|
||||
skipPlayersLoad: true,
|
||||
});
|
||||
|
||||
console.log('Cast SDK loaded');
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
}
|
||||
Reference in New Issue
Block a user