blob: 2bcb3af4a91bf0250b51a019457ef590cb526663 [file] [log] [blame]
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* Namespace for test related things.
*/
var test = test || {};
/**
* Extract the information of the given element.
* @param {Element} element Element to be extracted.
* @param {Window} contentWindow Window to be tested.
* @param {Array<string>=} opt_styleNames List of CSS property name to be
* obtained.
* @return {{attributes:Object<string>, text:string,
* styles:Object<string>, hidden:boolean}} Element
* information that contains contentText, attribute names and
* values, hidden attribute, and style names and values.
*/
function extractElementInfo(element, contentWindow, opt_styleNames) {
var attributes = {};
for (var i = 0; i < element.attributes.length; i++) {
attributes[element.attributes[i].nodeName] =
element.attributes[i].nodeValue;
}
var styles = {};
var styleNames = opt_styleNames || [];
var computedStyles = contentWindow.getComputedStyle(element);
for (var i = 0; i < styleNames.length; i++) {
styles[styleNames[i]] = computedStyles[styleNames[i]];
}
var text = element.textContent;
var size = element.getBoundingClientRect();
return {
attributes: attributes,
text: text,
value: element.value,
styles: styles,
// The hidden attribute is not in the element.attributes even if
// element.hasAttribute('hidden') is true.
hidden: !!element.hidden,
// These attributes are set when element is img or canvas.
imageWidth: Number(element.width),
imageHeight: Number(element.height),
// These attributes are set in any element.
renderedWidth: size.width,
renderedHeight: size.height
};
}
/**
* Namespace for test utility functions.
*
* Public functions in the test.util.sync and the test.util.async namespaces are
* published to test cases and can be called by using callRemoteTestUtil. The
* arguments are serialized as JSON internally. If application ID is passed to
* callRemoteTestUtil, the content window of the application is added as the
* first argument. The functions in the test.util.async namespace are passed the
* callback function as the last argument.
*/
test.util = {};
/**
* Namespace for synchronous utility functions.
*/
test.util.sync = {};
/**
* Namespace for asynchronous utility functions.
*/
test.util.async = {};
/**
* List of extension ID of the testing extension.
* @type {Array<string>}
* @const
*/
test.util.TESTING_EXTENSION_IDS = [
'oobinhbdbiehknkpbpejbbpdbkdjmoco', // File Manager test
'ejhcmmdhhpdhhgmifplfmjobgegbibkn', // Gallery test
'ljoplibgfehghmibaoaepfagnmbbfiga', // Video Player test
'ddabbgbggambiildohfagdkliahiecfl', // Audio Player test
];
/**
* Obtains window information.
*
* @return {Object<{innerWidth:number, innerHeight:number}>} Map window
* ID and window information.
*/
test.util.sync.getWindows = function() {
var windows = {};
for (var id in window.background.appWindows) {
var windowWrapper = window.background.appWindows[id];
windows[id] = {
outerWidth: windowWrapper.contentWindow.outerWidth,
outerHeight: windowWrapper.contentWindow.outerHeight
};
}
for (var id in window.background.dialogs) {
windows[id] = {
outerWidth: window.background.dialogs[id].outerWidth,
outerHeight: window.background.dialogs[id].outerHeight
};
}
return windows;
};
/**
* Closes the specified window.
*
* @param {string} appId AppId of window to be closed.
* @return {boolean} Result: True if success, false otherwise.
*/
test.util.sync.closeWindow = function(appId) {
if (appId in window.background.appWindows &&
window.background.appWindows[appId].contentWindow) {
window.background.appWindows[appId].close();
return true;
}
return false;
};
/**
* Gets a document in the Files.app's window, including iframes.
*
* @param {Window} contentWindow Window to be used.
* @param {string=} opt_iframeQuery Query for the iframe.
* @return {Document} Returns the found document or null if not found.
* @private
*/
test.util.sync.getDocument_ = function(contentWindow, opt_iframeQuery) {
if (opt_iframeQuery) {
var iframe = contentWindow.document.querySelector(opt_iframeQuery);
var doc = iframe && iframe.contentWindow && iframe.contentWindow.document;
return doc ? doc : null;
}
return contentWindow.document ? contentWindow.document : null;
};
/**
* Gets the element specified by |targetQuery|.
*
* @param {Window} contentWindow Window to be used.
* @param {string} targetQuery Query to specify the element.
* @param {string=} opt_iframeQuery Query for the iframe.
* @return {Element} If the specified element is not found, null is returned.
* @private
*/
test.util.sync.getElement_ = function(
contentWindow, targetQuery, opt_iframeQuery) {
var doc = test.util.sync.getDocument_(contentWindow, opt_iframeQuery);
if (!doc)
return null;
var target = doc.querySelector(targetQuery);
if (!target) {
console.error('Target element for ' + targetQuery + ' not found.');
return null;
}
return target;
};
/**
* Gets total Javascript error count from background page and each app window.
* @return {number} Error count.
*/
test.util.sync.getErrorCount = function() {
var totalCount = window.JSErrorCount;
for (var appId in window.background.appWindows) {
var contentWindow = window.background.appWindows[appId].contentWindow;
if (contentWindow.JSErrorCount)
totalCount += contentWindow.JSErrorCount;
}
return totalCount;
};
/**
* Resizes the window to the specified dimensions.
*
* @param {Window} contentWindow Window to be tested.
* @param {number} width Window width.
* @param {number} height Window height.
* @return {boolean} True for success.
*/
test.util.sync.resizeWindow = function(contentWindow, width, height) {
window.background.appWindows[contentWindow.appID].resizeTo(width, height);
return true;
};
/**
* Maximizes the window.
* @param {Window} contentWindow Window to be tested.
* @return {boolean} True for success.
*/
test.util.sync.maximizeWindow = function(contentWindow) {
window.background.appWindows[contentWindow.appID].maximize();
return true;
};
/**
* Restores the window state (maximized/minimized/etc...).
* @param {Window} contentWindow Window to be tested.
* @return {boolean} True for success.
*/
test.util.sync.restoreWindow = function(contentWindow) {
window.background.appWindows[contentWindow.appID].restore();
return true;
};
/**
* Returns whether the window is miximized or not.
* @param {Window} contentWindow Window to be tested.
* @return {boolean} True if the window is maximized now.
*/
test.util.sync.isWindowMaximized = function(contentWindow) {
return window.background.appWindows[contentWindow.appID].isMaximized();
};
/**
* Queries all elements.
*
* @param {Window} contentWindow Window to be tested.
* @param {string} targetQuery Query to specify the element.
* @param {?string} iframeQuery Iframe selector or null if no iframe.
* @param {Array<string>=} opt_styleNames List of CSS property name to be
* obtained.
* @return {Array<{attributes:Object<string>, text:string,
* styles:Object<string>, hidden:boolean}>} Element
* information that contains contentText, attribute names and
* values, hidden attribute, and style names and values.
*/
test.util.sync.queryAllElements = function(
contentWindow, targetQuery, iframeQuery, opt_styleNames) {
var doc = test.util.sync.getDocument_(
contentWindow, iframeQuery || undefined);
if (!doc)
return [];
// The return value of querySelectorAll is not an array.
return Array.prototype.map.call(
doc.querySelectorAll(targetQuery),
function(element) {
return extractElementInfo(element, contentWindow, opt_styleNames);
});
};
/**
* Get the information of the active element.
*
* @param {Window} contentWindow Window to be tested.
* @param {string} targetQuery Query to specify the element.
* @param {?string} iframeQuery Iframe selector or null if no iframe.
* @param {Array<string>=} opt_styleNames List of CSS property name to be
* obtained.
* @return {?{attributes:Object<string>, text:string,
* styles:Object<string>, hidden:boolean}} Element
* information that contains contentText, attribute names and
* values, hidden attribute, and style names and values. If there is no
* active element, returns null.
*/
test.util.sync.getActiveElement = function(
contentWindow, targetQuery, iframeQuery, opt_styleNames) {
var doc = test.util.sync.getDocument_(
contentWindow, iframeQuery || undefined);
if (!doc || !doc.activeElement)
return null;
return extractElementInfo(doc.activeElement, contentWindow, opt_styleNames);
};
/**
* Assigns the text to the input element.
* @param {Window} contentWindow Window to be tested.
* @param {string} query Query for the input element.
* @param {string} text Text to be assigned.
*/
test.util.sync.inputText = function(contentWindow, query, text) {
var input = contentWindow.document.querySelector(query);
input.value = text;
};
/**
* Sends an event to the element specified by |targetQuery| or active element.
*
* @param {Window} contentWindow Window to be tested.
* @param {?string} targetQuery Query to specify the element. If this value is
* null, an event is dispatched to active element of the document.
* @param {!Event} event Event to be sent.
* @param {string=} opt_iframeQuery Optional iframe selector.
* @return {boolean} True if the event is sent to the target, false otherwise.
*/
test.util.sync.sendEvent = function(
contentWindow, targetQuery, event, opt_iframeQuery) {
var target = targetQuery === null ?
contentWindow.document.activeElement :
test.util.sync.getElement_(contentWindow, targetQuery, opt_iframeQuery);
if (!target)
return false;
target.dispatchEvent(event);
return true;
};
/**
* Sends an fake event having the specified type to the target query.
*
* @param {Window} contentWindow Window to be tested.
* @param {string} targetQuery Query to specify the element.
* @param {string} eventType Type of event.
* @param {Object=} opt_additionalProperties Object contaning additional
* properties.
* @return {boolean} True if the event is sent to the target, false otherwise.
*/
test.util.sync.fakeEvent = function(contentWindow,
targetQuery,
eventType,
opt_additionalProperties) {
var event = new Event(eventType,
/** @type {!EventInit} */ (opt_additionalProperties || {}));
if (opt_additionalProperties) {
for (var name in opt_additionalProperties) {
event[name] = opt_additionalProperties[name];
}
}
return test.util.sync.sendEvent(contentWindow, targetQuery, event);
};
/**
* Sends a fake key event to the element specified by |targetQuery| or active
* element with the given |keyIdentifier| and optional |ctrl| modifier.
*
* @param {Window} contentWindow Window to be tested.
* @param {?string} targetQuery Query to specify the element. If this value is
* null, key event is dispatched to active element of the document.
* @param {string} key DOM UI Events key value.
* @param {string} keyIdentifier Identifier of the emulated key.
* @param {boolean} ctrl Whether CTRL should be pressed, or not.
* @param {boolean} shift whether SHIFT should be pressed, or not.
* @param {boolean} alt whether ALT should be pressed, or not.
* @param {string=} opt_iframeQuery Optional iframe selector.
* @return {boolean} True if the event is sent to the target, false otherwise.
*/
test.util.sync.fakeKeyDown = function(
contentWindow, targetQuery, key, keyIdentifier, ctrl, shift, alt,
opt_iframeQuery) {
var event = new KeyboardEvent('keydown',
{
bubbles: true,
key: key,
keyIdentifier: keyIdentifier,
ctrlKey: ctrl,
shiftKey: shift,
altKey: alt
});
return test.util.sync.sendEvent(
contentWindow, targetQuery, event, opt_iframeQuery);
};
/**
* Simulates a fake mouse click (left button, single click) on the element
* specified by |targetQuery|. If the element has the click method, just calls
* it. Otherwise, this sends 'mouseover', 'mousedown', 'mouseup' and 'click'
* events in turns.
*
* @param {Window} contentWindow Window to be tested.
* @param {string} targetQuery Query to specify the element.
* @param {string=} opt_iframeQuery Optional iframe selector.
* @return {boolean} True if the all events are sent to the target, false
* otherwise.
*/
test.util.sync.fakeMouseClick = function(
contentWindow, targetQuery, opt_iframeQuery) {
var mouseOverEvent = new MouseEvent('mouseover', {bubbles: true, detail: 1});
var resultMouseOver = test.util.sync.sendEvent(
contentWindow, targetQuery, mouseOverEvent, opt_iframeQuery);
var mouseDownEvent = new MouseEvent('mousedown', {bubbles: true, detail: 1});
var resultMouseDown = test.util.sync.sendEvent(
contentWindow, targetQuery, mouseDownEvent, opt_iframeQuery);
var mouseUpEvent = new MouseEvent('mouseup', {bubbles: true, detail: 1});
var resultMouseUp = test.util.sync.sendEvent(
contentWindow, targetQuery, mouseUpEvent, opt_iframeQuery);
var clickEvent = new MouseEvent('click', {bubbles: true, detail: 1});
var resultClick = test.util.sync.sendEvent(
contentWindow, targetQuery, clickEvent, opt_iframeQuery);
return resultMouseOver && resultMouseDown && resultMouseUp && resultClick;
};
/**
* Simulates a fake mouse click (right button, single click) on the element
* specified by |targetQuery|.
*
* @param {Window} contentWindow Window to be tested.
* @param {string} targetQuery Query to specify the element.
* @param {string=} opt_iframeQuery Optional iframe selector.
* @return {boolean} True if the event is sent to the target, false
* otherwise.
*/
test.util.sync.fakeMouseRightClick = function(
contentWindow, targetQuery, opt_iframeQuery) {
var mouseDownEvent = new MouseEvent('mousedown', {bubbles: true, button: 2});
if (!test.util.sync.sendEvent(contentWindow, targetQuery, mouseDownEvent,
opt_iframeQuery)) {
return false;
}
var contextMenuEvent = new MouseEvent('contextmenu', {bubbles: true});
return test.util.sync.sendEvent(contentWindow, targetQuery, contextMenuEvent,
opt_iframeQuery);
};
/**
* Simulates a fake double click event (left button) to the element specified by
* |targetQuery|.
*
* @param {Window} contentWindow Window to be tested.
* @param {string} targetQuery Query to specify the element.
* @param {string=} opt_iframeQuery Optional iframe selector.
* @return {boolean} True if the event is sent to the target, false otherwise.
*/
test.util.sync.fakeMouseDoubleClick = function(
contentWindow, targetQuery, opt_iframeQuery) {
// Double click is always preceded with a single click.
if (!test.util.sync.fakeMouseClick(
contentWindow, targetQuery, opt_iframeQuery)) {
return false;
}
// Send the second click event, but with detail equal to 2 (number of clicks)
// in a row.
var event = new MouseEvent('click', { bubbles: true, detail: 2 });
if (!test.util.sync.sendEvent(
contentWindow, targetQuery, event, opt_iframeQuery)) {
return false;
}
// Send the double click event.
var event = new MouseEvent('dblclick', { bubbles: true });
if (!test.util.sync.sendEvent(
contentWindow, targetQuery, event, opt_iframeQuery)) {
return false;
}
return true;
};
/**
* Sends a fake mouse down event to the element specified by |targetQuery|.
*
* @param {Window} contentWindow Window to be tested.
* @param {string} targetQuery Query to specify the element.
* @param {string=} opt_iframeQuery Optional iframe selector.
* @return {boolean} True if the event is sent to the target, false otherwise.
*/
test.util.sync.fakeMouseDown = function(
contentWindow, targetQuery, opt_iframeQuery) {
var event = new MouseEvent('mousedown', { bubbles: true });
return test.util.sync.sendEvent(
contentWindow, targetQuery, event, opt_iframeQuery);
};
/**
* Sends a fake mouse up event to the element specified by |targetQuery|.
*
* @param {Window} contentWindow Window to be tested.
* @param {string} targetQuery Query to specify the element.
* @param {string=} opt_iframeQuery Optional iframe selector.
* @return {boolean} True if the event is sent to the target, false otherwise.
*/
test.util.sync.fakeMouseUp = function(
contentWindow, targetQuery, opt_iframeQuery) {
var event = new MouseEvent('mouseup', { bubbles: true });
return test.util.sync.sendEvent(
contentWindow, targetQuery, event, opt_iframeQuery);
};
/**
* Focuses to the element specified by |targetQuery|. This method does not
* provide any guarantee whether the element is actually focused or not.
*
* @param {Window} contentWindow Window to be tested.
* @param {string} targetQuery Query to specify the element.
* @param {string=} opt_iframeQuery Optional iframe selector.
* @return {boolean} True if focus method of the element has been called, false
* otherwise.
*/
test.util.sync.focus = function(contentWindow, targetQuery, opt_iframeQuery) {
var target = test.util.sync.getElement_(
contentWindow, targetQuery, opt_iframeQuery);
if (!target)
return false;
target.focus();
return true;
};
/**
* Obtains the list of notification ID.
* @param {function(Object<boolean>)} callback Callback function with
* results returned by the script.
*/
test.util.async.getNotificationIDs = function(callback) {
chrome.notifications.getAll(callback);
};
/**
* Gets file entries just under the volume.
*
* @param {VolumeManagerCommon.VolumeType} volumeType Volume type.
* @param {Array<string>} names File name list.
* @param {function(*)} callback Callback function with results returned by the
* script.
*/
test.util.async.getFilesUnderVolume = function(volumeType, names, callback) {
var displayRootPromise =
VolumeManager.getInstance().then(function(volumeManager) {
var volumeInfo = volumeManager.getCurrentProfileVolumeInfo(volumeType);
return volumeInfo.resolveDisplayRoot();
});
var retrievePromise = displayRootPromise.then(function(displayRoot) {
var filesPromise = names.map(function(name) {
return new Promise(
displayRoot.getFile.bind(displayRoot, name, {}));
});
return Promise.all(filesPromise).then(function(aa) {
return util.entriesToURLs(aa);
}).catch(function() {
return [];
});
});
retrievePromise.then(callback);
};
/**
* Registers message listener, which runs test utility functions.
*/
test.util.registerRemoteTestUtils = function() {
// Return true for asynchronous functions and false for synchronous.
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
// Check the sender.
if (!sender.id ||
test.util.TESTING_EXTENSION_IDS.indexOf(sender.id) === -1) {
// Silently return. Don't return false; that short-circuits the
// propagation of messages, and there are now other listeners that want to
// handle external messages.
return;
}
// Set a global flag that we are in tests, so other components are aware
// of it.
window.IN_TEST = true;
// Check the function name.
if (!request.func || request.func[request.func.length - 1] == '_') {
request.func = '';
}
// Prepare arguments.
if (!('args' in request))
throw new Error('Invalid request.');
var args = request.args.slice(); // shallow copy
if (request.appId) {
if (window.background.appWindows[request.appId]) {
args.unshift(window.background.appWindows[request.appId].contentWindow);
} else if (window.background.dialogs[request.appId]) {
args.unshift(window.background.dialogs[request.appId]);
} else {
console.error('Specified window not found: ' + request.appId);
return false;
}
}
// Call the test utility function and respond the result.
if (test.util.async[request.func]) {
args[test.util.async[request.func].length - 1] = function() {
console.debug('Received the result of ' + request.func);
sendResponse.apply(null, arguments);
};
console.debug('Waiting for the result of ' + request.func);
test.util.async[request.func].apply(null, args);
return true;
} else if (test.util.sync[request.func]) {
sendResponse(test.util.sync[request.func].apply(null, args));
return false;
} else {
console.error('Invalid function name.');
return false;
}
});
};