blob: 7183dd3d149b6c1af25fce3d7586e04ec9ef1a0f [file] [log] [blame]
// Copyright (c) 2012 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.
/**
* @fileoverview The way these tests work is as follows:
* C++ in net_internals_ui_browsertest.cc does any necessary setup, and then
* calls the entry point for a test with RunJavascriptTest. The called
* function can then use the assert/expect functions defined in test_api.js.
* All callbacks from the browser are wrapped in such a way that they can
* also use the assert/expect functions.
*
* A test ends when testDone is called. This can be done by the test itself,
* but will also be done by the test framework when an assert/expect test fails
* or an exception is thrown.
*/
var NetInternalsTest = (function() {
/**
* Private pointer to the currently active test framework. Needed so static
* functions can access some of the inner workings of the test framework.
* @type {NetInternalsTest}
*/
var activeTest_ = null;
function NetInternalsTest() {
activeTest_ = this;
}
NetInternalsTest.prototype = {
/**
* Define the C++ fixture class and include it.
* @type {?string}
* @override
*/
typedefCppFixture: 'NetInternalsTest',
/** @inheritDoc */
browsePreload: 'chrome://net-internals/',
/** @inheritDoc */
isAsync: true,
setUp: function() {
testing.Test.prototype.setUp.call(this);
// Enforce accessibility auditing, but suppress some false positives.
this.accessibilityIssuesAreErrors = true;
// False positive because a unicode character is used to draw a square.
// If it was actual text it'd be too low-contrast, but a square is fine.
this.accessibilityAuditConfig.ignoreSelectors(
'lowContrastElements', '#timeline-view-selection-ul label');
// False positives for unknown reason.
this.accessibilityAuditConfig.ignoreSelectors(
'focusableElementNotVisibleAndNotAriaHidden',
'#domain-security-policy-view-tab-content *');
// TODO(aboxhall): enable when this bug is fixed:
// https://github.com/GoogleChrome/accessibility-developer-tools/issues/69
this.accessibilityAuditConfig.auditRulesToIgnore.push(
'focusableElementNotVisibleAndNotAriaHidden');
// Enable when warning is resolved.
// AX_HTML_01: http://crbug.com/559204
this.accessibilityAuditConfig.ignoreSelectors('humanLangMissing', 'html');
// Wrap g_browser.receive around a test function so that assert and expect
// functions can be called from observers.
g_browser.receive = this.continueTest(
WhenTestDone.EXPECT, BrowserBridge.prototype.receive.bind(g_browser));
var runTest = this.deferRunTest(WhenTestDone.EXPECT);
// If we've already received the constants, start the tests.
if (Constants) {
// Stat test asynchronously, to avoid running a nested test function.
window.setTimeout(runTest, 0);
return;
}
// Otherwise, wait until we do.
console.log('Received constants late.');
/**
* Observer that starts the tests once we've received the constants.
*/
function ConstantsObserver() {
this.testStarted_ = false;
}
ConstantsObserver.prototype.onReceivedConstants = function() {
if (!this.testStarted_) {
this.testStarted_ = true;
// Stat test asynchronously, to avoid running a nested test function,
// and so we don't call any constants observers used by individual
// tests.
window.setTimeout(runTest, 0);
}
};
g_browser.addConstantsObserver(new ConstantsObserver());
}
};
/**
* A callback function for use by asynchronous Tasks that need a return value
* from the NetInternalsTest::MessageHandler. Must be null when no such
* Task is running. Set by NetInternalsTest.setCallback. Automatically
* cleared once called. Must only be called through
* NetInternalsTest::MessageHandler::RunCallback, from the browser process.
*/
NetInternalsTest.callback = null;
/**
* Sets NetInternalsTest.callback. Any arguments will be passed to the
* callback function.
* @param {function} callbackFunction Callback function to be called from the
* browser.
*/
NetInternalsTest.setCallback = function(callbackFunction) {
// Make sure no Task has already set the callback function.
chai.assert.strictEqual(null, NetInternalsTest.callback);
// Wrap |callbackFunction| in a function that clears
// |NetInternalsTest.callback| before calling |callbackFunction|.
var callbackFunctionWrapper = function() {
NetInternalsTest.callback = null;
callbackFunction.apply(null, Array.prototype.slice.call(arguments));
};
// Wrap |callbackFunctionWrapper| with test framework code.
NetInternalsTest.callback =
activeTest_.continueTest(WhenTestDone.EXPECT, callbackFunctionWrapper);
};
/**
* Returns the first tbody that's a descendant of |ancestorId|. If the
* specified node is itself a table body node, just returns that node.
* Returns null if no such node is found.
* @param {string} ancestorId ID of an HTML element with a tbody descendant.
* @return {node} The tbody node, or null.
*/
NetInternalsTest.getTbodyDescendent = function(ancestorId) {
if ($(ancestorId).nodeName == 'TBODY')
return $(ancestorId);
// The tbody element of the first styled table in |parentId|.
return document.querySelector('#' + ancestorId + ' tbody');
};
/**
* Finds the first tbody that's a descendant of |ancestorId|, including the
* |ancestorId| element itself, and returns the number of rows it has.
* Returns -1 if there's no such table. Excludes hidden rows.
* @param {string} ancestorId ID of an HTML element with a tbody descendant.
* @return {number} Number of rows the style table's body has.
*/
NetInternalsTest.getTbodyNumRows = function(ancestorId) {
// The tbody element of the first styled table in |parentId|.
var tbody = NetInternalsTest.getTbodyDescendent(ancestorId);
if (!tbody)
return -1;
var visibleChildren = 0;
for (var i = 0; i < tbody.children.length; ++i) {
if (NetInternalsTest.nodeIsVisible(tbody.children[i]))
++visibleChildren;
}
return visibleChildren;
};
/**
* Finds the first tbody that's a descendant of |ancestorId|, including the
* |ancestorId| element itself, and checks if it has exactly |expectedRows|
* rows. Does not count hidden rows.
* @param {string} ancestorId ID of an HTML element with a tbody descendant.
* @param {number} expectedRows Expected number of rows in the table.
*/
NetInternalsTest.checkTbodyRows = function(ancestorId, expectedRows) {
chai.assert.strictEqual(
expectedRows, NetInternalsTest.getTbodyNumRows(ancestorId),
'Incorrect number of rows in ' + ancestorId);
};
/**
* Finds the tbody that's a descendant of |ancestorId|, including the
* |ancestorId| element itself, and returns the text of the specified cell.
* If the cell does not exist, throws an exception. Skips over hidden rows.
* @param {string} ancestorId ID of an HTML element with a tbody descendant.
* @param {number} row Row of the value to retrieve.
* @param {number} column Column of the value to retrieve.
*/
NetInternalsTest.getTbodyText = function(ancestorId, row, column) {
var tbody = NetInternalsTest.getTbodyDescendent(ancestorId);
var currentChild = tbody.children[0];
while (currentChild) {
if (NetInternalsTest.nodeIsVisible(currentChild)) {
if (row == 0)
return currentChild.children[column].innerText;
--row;
}
currentChild = currentChild.nextElementSibling;
}
return 'invalid row';
};
/**
* Returns the view and menu item node for the tab with given id.
* Asserts if the tab can't be found.
* @param {string}: tabId Id of the tab to lookup.
* @return {Object}
*/
NetInternalsTest.getTab = function(tabId) {
var tabSwitcher = MainView.getInstance().tabSwitcher();
var view = tabSwitcher.getTabView(tabId);
var tabLink = tabSwitcher.tabIdToLink_[tabId];
chai.assert.notStrictEqual(view, undefined, tabId + ' does not exist.');
chai.assert.notStrictEqual(tabLink, undefined, tabId + ' does not exist.');
return {
view: view,
tabLink: tabLink,
};
};
/**
* Returns true if the node is visible.
* @param {Node}: node Node to check the visibility state of.
* @return {bool} Whether or not the node is visible.
*/
NetInternalsTest.nodeIsVisible = function(node) {
return node.style.display != 'none';
};
/**
* Returns true if the specified tab's link is visible, false otherwise.
* Asserts if the link can't be found.
* @param {string}: tabId Id of the tab to check.
* @return {bool} Whether or not the tab's link is visible.
*/
NetInternalsTest.tabLinkIsVisible = function(tabId) {
var tabLink = NetInternalsTest.getTab(tabId).tabLink;
return NetInternalsTest.nodeIsVisible(tabLink);
};
/**
* Returns the id of the currently active tab.
* @return {string} ID of the active tab.
*/
NetInternalsTest.getActiveTabId = function() {
return MainView.getInstance().tabSwitcher().getActiveTabId();
};
/**
* Returns the tab id of a tab, given its associated URL hash value. Asserts
* if |hash| has no associated tab.
* @param {string}: hash Hash associated with the tab to return the id of.
* @return {string} String identifier of the tab with the given hash.
*/
NetInternalsTest.getTabId = function(hash) {
/**
* Map of tab ids to location hashes. Since the text fixture must be
* runnable independent of net-internals, for generating the test's cc
* files, must be careful to only create this map while a test is running.
* @type {object.<string, string>}
*/
var hashToTabIdMap = {
import: ImportView.TAB_ID,
proxy: ProxyView.TAB_ID,
events: EventsView.TAB_ID,
timeline: TimelineView.TAB_ID,
dns: DnsView.TAB_ID,
sockets: SocketsView.TAB_ID,
http2: SpdyView.TAB_ID,
'alt-svc': AltSvcView.TAB_ID,
quic: QuicView.TAB_ID,
reporting: ReportingView.TAB_ID,
httpCache: HttpCacheView.TAB_ID,
modules: ModulesView.TAB_ID,
prerender: PrerenderView.TAB_ID,
};
chai.assert.strictEqual(
typeof hashToTabIdMap[hash], 'string', 'Invalid tab anchor: ' + hash);
var tabId = hashToTabIdMap[hash];
chai.assert.strictEqual(
'object', typeof NetInternalsTest.getTab(tabId),
'Invalid tab: ' + tabId);
return tabId;
};
/**
* Switches to the specified tab.
* @param {string}: hash Hash associated with the tab to switch to.
*/
NetInternalsTest.switchToView = function(hash) {
var tabId = NetInternalsTest.getTabId(hash);
// Make sure the tab link is visible, as we only simulate normal usage.
chai.assert.isTrue(
NetInternalsTest.tabLinkIsVisible(tabId),
tabId + ' does not have a visible tab link.');
var tabLinkNode = NetInternalsTest.getTab(tabId).tabLink;
// Simulate a left click on the link.
var mouseEvent = document.createEvent('MouseEvents');
mouseEvent.initMouseEvent(
'click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false,
0, null);
tabLinkNode.dispatchEvent(mouseEvent);
// Make sure the hash changed.
chai.assert.strictEqual('#' + hash, document.location.hash);
// Run the onhashchange function, so can test the resulting state.
// Otherwise, the method won't trigger synchronously.
window.onhashchange();
// Make sure only the specified tab is visible.
var tabSwitcher = MainView.getInstance().tabSwitcher();
var tabIdToView = tabSwitcher.getAllTabViews();
for (var curTabId in tabIdToView) {
chai.assert.strictEqual(
curTabId == tabId, tabSwitcher.getTabView(curTabId).isVisible(),
curTabId + ': Unexpected visibility state.');
}
};
/**
* Checks the visibility of all tab links against expected values.
* @param {object.<string, bool>}: tabVisibilityState Object with a an entry
* for each tab's hash, and a bool indicating if it should be visible or
* not.
* @param {bool+}: tourTabs True if tabs expected to be visible should should
* each be navigated to as well.
*/
NetInternalsTest.checkTabLinkVisibility = function(
tabVisibilityState, tourTabs) {
// The currently active tab should have a link that is visible.
chai.assert.isTrue(
NetInternalsTest.tabLinkIsVisible(NetInternalsTest.getActiveTabId()));
// Check visibility state of all tabs.
var tabCount = 0;
for (var hash in tabVisibilityState) {
var tabId = NetInternalsTest.getTabId(hash);
chai.assert.strictEqual(
'object', typeof NetInternalsTest.getTab(tabId),
'Invalid tab: ' + tabId);
chai.assert.strictEqual(
tabVisibilityState[hash], NetInternalsTest.tabLinkIsVisible(tabId),
tabId + ' visibility state is unexpected.');
tabCount++;
}
// Check that every tab was listed.
var tabSwitcher = MainView.getInstance().tabSwitcher();
var tabIdToView = tabSwitcher.getAllTabViews();
var expectedTabCount = 0;
for (tabId in tabIdToView)
expectedTabCount++;
chai.assert.strictEqual(tabCount, expectedTabCount);
};
/**
* This class allows multiple Tasks to be queued up to be run sequentially.
* A Task can wait for asynchronous callbacks from the browser before
* completing, at which point the next queued Task will be run.
* @param {bool}: endTestWhenDone True if testDone should be called when the
* final task completes.
* @param {NetInternalsTest}: test Test fixture passed to Tasks.
* @constructor
*/
NetInternalsTest.TaskQueue = function(endTestWhenDone) {
// Test fixture for passing to tasks.
this.tasks_ = [];
this.isRunning_ = false;
this.endTestWhenDone_ = endTestWhenDone;
};
NetInternalsTest.TaskQueue.prototype = {
/**
* Adds a Task to the end of the queue. Each Task may only be added once
* to a single queue. Also passes the text fixture to the Task.
* @param {Task}: task The Task to add.
*/
addTask: function(task) {
this.tasks_.push(task);
task.setTaskQueue_(this);
},
/**
* Adds a Task to the end of the queue. The task will call the provided
* function, and then complete.
* @param {function}: taskFunction The function the task will call.
*/
addFunctionTask: function(taskFunction) {
this.addTask(new NetInternalsTest.CallFunctionTask(taskFunction));
},
/**
* Starts running the Tasks in the queue, passing its arguments, if any,
* to the first Task's start function. May only be called once.
*/
run: function() {
chai.assert.isFalse(this.isRunning_);
this.isRunning_ = true;
this.runNextTask_(Array.prototype.slice.call(arguments));
},
/**
* If there are any Tasks in |tasks_|, removes the first one and runs it.
* Otherwise, sets |isRunning_| to false. If |endTestWhenDone_| is true,
* calls testDone.
* @param {array} argArray arguments to be passed on to next Task's start
* method. May be a 0-length array.
*/
runNextTask_: function(argArray) {
chai.assert.isTrue(this.isRunning_);
// The last Task may have used |NetInternalsTest.callback|. Make sure
// it's now null.
expectEquals(null, NetInternalsTest.callback);
if (this.tasks_.length > 0) {
var nextTask = this.tasks_.shift();
nextTask.start.apply(nextTask, argArray);
} else {
this.isRunning_ = false;
if (this.endTestWhenDone_)
testDone();
}
}
};
/**
* A Task that can be added to a TaskQueue. A Task is started with a call to
* the start function, and must call its own onTaskDone when complete.
* @constructor
*/
NetInternalsTest.Task = function() {
this.taskQueue_ = null;
this.isDone_ = false;
this.completeAsync_ = false;
};
NetInternalsTest.Task.prototype = {
/**
* Starts running the Task. Only called once per Task, must be overridden.
* Any arguments passed to the last Task's onTaskDone, or to run (If the
* first task) will be passed along.
*/
start: function() {
assertNotReached('Start function not overridden.');
},
/**
* Updates value of |completeAsync_|. If set to true, the next Task will
* start asynchronously. Useful if the Task completes on an event that
* the next Task may care about.
*/
setCompleteAsync: function(value) {
this.completeAsync_ = value;
},
/**
* @return {bool} True if this task has completed by calling onTaskDone.
*/
isDone: function() {
return this.isDone_;
},
/**
* Sets the TaskQueue used by the task in the onTaskDone function. May only
* be called by the TaskQueue.
* @param {TaskQueue}: taskQueue The TaskQueue |this| has been added to.
*/
setTaskQueue_: function(taskQueue) {
chai.assert.strictEqual(null, this.taskQueue_);
this.taskQueue_ = taskQueue;
},
/**
* Must be called when a task is complete, and can only be called once for a
* task. Runs the next task, if any, passing along all arguments.
*/
onTaskDone: function() {
chai.assert.isFalse(this.isDone_);
this.isDone_ = true;
// Function to run the next task in the queue.
var runNextTask = this.taskQueue_.runNextTask_.bind(
this.taskQueue_, Array.prototype.slice.call(arguments));
// If we need to start the next task asynchronously, we need to wrap
// it with the test framework code.
if (this.completeAsync_) {
window.setTimeout(
activeTest_.continueTest(WhenTestDone.EXPECT, runNextTask), 0);
return;
}
// Otherwise, just run the next task directly.
runNextTask();
},
};
/**
* A Task that can be added to a TaskQueue. A Task is started with a call to
* the start function, and must call its own onTaskDone when complete.
* @extends {NetInternalsTest.Task}
* @constructor
*/
NetInternalsTest.CallFunctionTask = function(taskFunction) {
NetInternalsTest.Task.call(this);
chai.assert.strictEqual('function', typeof taskFunction);
this.taskFunction_ = taskFunction;
};
NetInternalsTest.CallFunctionTask.prototype = {
__proto__: NetInternalsTest.Task.prototype,
/**
* Runs the function and then completes. Passes all arguments, if any,
* along to the function.
*/
start: function() {
this.taskFunction_.apply(null, Array.prototype.slice.call(arguments));
this.onTaskDone();
}
};
/**
* Returns true if a node does not have a 'display' property of 'none'.
* @param {node}: node The node to check.
*/
NetInternalsTest.isDisplayed = function(node) {
var style = getComputedStyle(node);
return style.getPropertyValue('display') != 'none';
};
/**
* Creates a new NetLog source. Note that the id may conflict with events
* received from the browser.
* @param {int}: type The source type.
* @param {int}: id The source id.
* @constructor
*/
NetInternalsTest.Source = function(type, id) {
chai.assert.notStrictEqual(getKeyWithValue(EventSourceType, type), '?');
assertGE(id, 0);
this.type = type;
this.id = id;
};
/**
* Creates a new NetLog event.
* @param {Source}: source The source associated with the event.
* @param {int}: type The event id.
* @param {int}: time When the event occurred.
* @param {int}: phase The event phase.
* @param {object}: params The event parameters. May be null.
* @constructor
*/
NetInternalsTest.Event = function(source, type, time, phase, params) {
chai.assert.notStrictEqual(getKeyWithValue(EventType, type), '?');
chai.assert.notStrictEqual(getKeyWithValue(EventPhase, phase), '?');
this.source = source;
this.phase = phase;
this.type = type;
this.time = '' + time;
this.phase = phase;
if (params)
this.params = params;
};
/**
* Creates a new NetLog begin event. Parameters are the same as Event,
* except there's no |phase| argument.
* @see Event
*/
NetInternalsTest.createBeginEvent = function(source, type, time, params) {
return new NetInternalsTest.Event(
source, type, time, EventPhase.PHASE_BEGIN, params);
};
/**
* Creates a new NetLog end event. Parameters are the same as Event,
* except there's no |phase| argument.
* @see Event
*/
NetInternalsTest.createEndEvent = function(source, type, time, params) {
return new NetInternalsTest.Event(
source, type, time, EventPhase.PHASE_END, params);
};
/**
* Creates a new NetLog end event matching the given begin event.
* @param {Event}: beginEvent The begin event. Returned event will have the
* same source and type.
* @param {int}: time When the event occurred.
* @param {object}: params The event parameters. May be null.
* @see Event
*/
NetInternalsTest.createMatchingEndEvent = function(beginEvent, time, params) {
return NetInternalsTest.createEndEvent(
beginEvent.source, beginEvent.type, time, params);
};
/**
* Checks that only the given status view node is visible.
* @param {string}: nodeId ID of the node that should be visible.
*/
NetInternalsTest.expectStatusViewNodeVisible = function(nodeId) {
var allIds = [
CaptureStatusView.MAIN_BOX_ID, LoadedStatusView.MAIN_BOX_ID,
HaltedStatusView.MAIN_BOX_ID
];
for (var i = 0; i < allIds.length; ++i) {
var curId = allIds[i];
chai.assert.strictEqual(nodeId == curId,
NetInternalsTest.nodeIsVisible($(curId)));
}
};
return NetInternalsTest;
})();