1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * @fileoverview The way these tests work is as follows:
7 * C++ in net_internals_ui_browsertest.cc does any necessary setup, and then
8 * calls the entry point for a test with RunJavascriptTest. The called
9 * function can then use the assert/expect functions defined in test_api.js.
10 * All callbacks from the browser are wrapped in such a way that they can
11 * also use the assert/expect functions.
13 * A test ends when testDone is called. This can be done by the test itself,
14 * but will also be done by the test framework when an assert/expect test fails
15 * or an exception is thrown.
18 // Include the C++ browser test class when generating *.cc files.
20 '"chrome/browser/ui/webui/net_internals/net_internals_ui_browsertest.h"');
22 var NetInternalsTest = (function() {
24 * A shorter poll interval is used for tests, since a few tests wait for
25 * polled values to change.
29 var TESTING_POLL_INTERVAL_MS = 50;
32 * Private pointer to the currently active test framework. Needed so static
33 * functions can access some of the inner workings of the test framework.
34 * @type {NetInternalsTest}
36 var activeTest_ = null;
38 function NetInternalsTest() {
42 NetInternalsTest.prototype = {
43 __proto__: testing.Test.prototype,
46 * Define the C++ fixture class and include it.
50 typedefCppFixture: 'NetInternalsTest',
53 browsePreload: 'chrome://net-internals/',
59 // Enforce accessibility auditing, but suppress some false positives.
60 this.accessibilityIssuesAreErrors = true;
61 // False positive because a unicode character is used to draw a square.
62 // If it was actual text it'd be too low-contrast, but a square is fine.
63 this.accessibilityAuditConfig.ignoreSelectors(
64 'lowContrastElements', '#timeline-view-selection-ul label');
65 // Suppress this error; the black-on-gray button is readable.
66 this.accessibilityAuditConfig.ignoreSelectors(
67 'lowContrastElements', '#export-view-save-log-file');
68 // False positive because the background color highlights and then
69 // fades out with a transition when there's an error.
70 this.accessibilityAuditConfig.ignoreSelectors(
71 'lowContrastElements', '#hsts-view-query-output span');
72 // False positives for unknown reason.
73 this.accessibilityAuditConfig.ignoreSelectors(
74 'focusableElementNotVisibleAndNotAriaHidden',
75 '#hsts-view-tab-content *');
77 // Wrap g_browser.receive around a test function so that assert and expect
78 // functions can be called from observers.
80 this.continueTest(WhenTestDone.EXPECT,
81 BrowserBridge.prototype.receive.bind(g_browser));
83 g_browser.setPollInterval(TESTING_POLL_INTERVAL_MS);
85 var runTest = this.deferRunTest(WhenTestDone.EXPECT);
87 // If we've already received the constants, start the tests.
89 // Stat test asynchronously, to avoid running a nested test function.
90 window.setTimeout(runTest, 0);
94 // Otherwise, wait until we do.
95 console.log('Received constants late.');
98 * Observer that starts the tests once we've received the constants.
100 function ConstantsObserver() {
101 this.testStarted_ = false;
104 ConstantsObserver.prototype.onReceivedConstants = function() {
105 if (!this.testStarted_) {
106 this.testStarted_ = true;
107 // Stat test asynchronously, to avoid running a nested test function,
108 // and so we don't call any constants observers used by individual
110 window.setTimeout(runTest, 0);
114 g_browser.addConstantsObserver(new ConstantsObserver());
119 * A callback function for use by asynchronous Tasks that need a return value
120 * from the NetInternalsTest::MessageHandler. Must be null when no such
121 * Task is running. Set by NetInternalsTest.setCallback. Automatically
122 * cleared once called. Must only be called through
123 * NetInternalsTest::MessageHandler::RunCallback, from the browser process.
125 NetInternalsTest.callback = null;
128 * Sets NetInternalsTest.callback. Any arguments will be passed to the
130 * @param {function} callbackFunction Callback function to be called from the
133 NetInternalsTest.setCallback = function(callbackFunction) {
134 // Make sure no Task has already set the callback function.
135 assertEquals(null, NetInternalsTest.callback);
137 // Wrap |callbackFunction| in a function that clears
138 // |NetInternalsTest.callback| before calling |callbackFunction|.
139 var callbackFunctionWrapper = function() {
140 NetInternalsTest.callback = null;
141 callbackFunction.apply(null, Array.prototype.slice.call(arguments));
144 // Wrap |callbackFunctionWrapper| with test framework code.
145 NetInternalsTest.callback =
146 activeTest_.continueTest(WhenTestDone.EXPECT, callbackFunctionWrapper);
150 * Returns the first tbody that's a descendant of |ancestorId|. If the
151 * specified node is itself a table body node, just returns that node.
152 * Returns null if no such node is found.
153 * @param {string} ancestorId ID of an HTML element with a tbody descendant.
154 * @return {node} The tbody node, or null.
156 NetInternalsTest.getTbodyDescendent = function(ancestorId) {
157 if ($(ancestorId).nodeName == 'TBODY')
158 return $(ancestorId);
159 // The tbody element of the first styled table in |parentId|.
160 return document.querySelector('#' + ancestorId + ' tbody');
164 * Finds the first tbody that's a descendant of |ancestorId|, including the
165 * |ancestorId| element itself, and returns the number of rows it has.
166 * Returns -1 if there's no such table. Excludes hidden rows.
167 * @param {string} ancestorId ID of an HTML element with a tbody descendant.
168 * @return {number} Number of rows the style table's body has.
170 NetInternalsTest.getTbodyNumRows = function(ancestorId) {
171 // The tbody element of the first styled table in |parentId|.
172 var tbody = NetInternalsTest.getTbodyDescendent(ancestorId);
175 var visibleChildren = 0;
176 for (var i = 0; i < tbody.children.length; ++i) {
177 if (NetInternalsTest.nodeIsVisible(tbody.children[i]))
180 return visibleChildren;
184 * Finds the first tbody that's a descendant of |ancestorId|, including the
185 * |ancestorId| element itself, and checks if it has exactly |expectedRows|
186 * rows. Does not count hidden rows.
187 * @param {string} ancestorId ID of an HTML element with a tbody descendant.
188 * @param {number} expectedRows Expected number of rows in the table.
190 NetInternalsTest.checkTbodyRows = function(ancestorId, expectedRows) {
191 expectEquals(expectedRows,
192 NetInternalsTest.getTbodyNumRows(ancestorId),
193 'Incorrect number of rows in ' + ancestorId);
197 * Finds the tbody that's a descendant of |ancestorId|, including the
198 * |ancestorId| element itself, and returns the text of the specified cell.
199 * If the cell does not exist, throws an exception. Skips over hidden rows.
200 * @param {string} ancestorId ID of an HTML element with a tbody descendant.
201 * @param {number} row Row of the value to retrieve.
202 * @param {number} column Column of the value to retrieve.
204 NetInternalsTest.getTbodyText = function(ancestorId, row, column) {
205 var tbody = NetInternalsTest.getTbodyDescendent(ancestorId);
206 var currentChild = tbody.children[0];
207 while (currentChild) {
208 if (NetInternalsTest.nodeIsVisible(currentChild)) {
210 return currentChild.children[column].innerText;
213 currentChild = currentChild.nextElementSibling;
215 return 'invalid row';
219 * Returns the view and menu item node for the tab with given id.
220 * Asserts if the tab can't be found.
221 * @param {string}: tabId Id of the tab to lookup.
224 NetInternalsTest.getTab = function(tabId) {
225 var tabSwitcher = MainView.getInstance().tabSwitcher();
226 var view = tabSwitcher.getTabView(tabId);
227 var menuItem = tabSwitcher.getMenuItemNode_(tabId);
229 assertNotEquals(view, undefined, tabId + ' does not exist.');
230 assertNotEquals(menuItem, undefined, tabId + ' does not exist.');
239 * Returns true if the node is visible.
240 * @param {Node}: node Node to check the visibility state of.
241 * @return {bool} Whether or not the node is visible.
243 NetInternalsTest.nodeIsVisible = function(node) {
244 return node.style.display != 'none';
248 * Returns true if the specified tab's handle is visible, false otherwise.
249 * Asserts if the handle can't be found.
250 * @param {string}: tabId Id of the tab to check.
251 * @return {bool} Whether or not the tab's handle is visible.
253 NetInternalsTest.tabHandleIsVisible = function(tabId) {
254 var tabHandleNode = NetInternalsTest.getTab(tabId).menuItem;
255 return NetInternalsTest.nodeIsVisible(tabHandleNode);
259 * Returns the id of the currently active tab.
260 * @return {string} ID of the active tab.
262 NetInternalsTest.getActiveTabId = function() {
263 return MainView.getInstance().tabSwitcher().getActiveTabId();
267 * Returns the tab id of a tab, given its associated URL hash value. Asserts
268 * if |hash| has no associated tab.
269 * @param {string}: hash Hash associated with the tab to return the id of.
270 * @return {string} String identifier of the tab with the given hash.
272 NetInternalsTest.getTabId = function(hash) {
274 * Map of tab handle names to location hashes. Since the text fixture
275 * must be runnable independent of net-internals, for generating the
276 * test's cc files, must be careful to only create this map while a test
278 * @type {object.<string, string>}
280 var hashToTabHandleIdMap = {
281 capture: CaptureView.TAB_ID,
282 export: ExportView.TAB_ID,
283 import: ImportView.TAB_ID,
284 proxy: ProxyView.TAB_ID,
285 events: EventsView.TAB_ID,
286 waterfall: WaterfallView.TAB_ID,
287 timeline: TimelineView.TAB_ID,
289 sockets: SocketsView.TAB_ID,
290 spdy: SpdyView.TAB_ID,
291 quic: QuicView.TAB_ID,
292 httpPipeline: HttpPipelineView.TAB_ID,
293 httpCache: HttpCacheView.TAB_ID,
294 modules: ModulesView.TAB_ID,
295 tests: TestView.TAB_ID,
296 hsts: HSTSView.TAB_ID,
297 logs: LogsView.TAB_ID,
298 prerender: PrerenderView.TAB_ID,
299 bandwidth: BandwidthView.TAB_ID,
300 chromeos: CrosView.TAB_ID,
301 visualizer: CrosLogVisualizerView.TAB_ID
304 assertEquals(typeof hashToTabHandleIdMap[hash], 'string',
305 'Invalid tab anchor: ' + hash);
306 var tabId = hashToTabHandleIdMap[hash];
307 assertEquals('object', typeof NetInternalsTest.getTab(tabId),
308 'Invalid tab: ' + tabId);
313 * Switches to the specified tab.
314 * @param {string}: hash Hash associated with the tab to switch to.
316 NetInternalsTest.switchToView = function(hash) {
317 var tabId = NetInternalsTest.getTabId(hash);
319 // Make sure the tab handle is visible, as we only simulate normal usage.
320 expectTrue(NetInternalsTest.tabHandleIsVisible(tabId),
321 tabId + ' does not have a visible tab handle.');
322 var tabHandleNode = NetInternalsTest.getTab(tabId).menuItem;
324 // Simulate selecting the menuitem.
325 tabHandleNode.selected = true;
326 tabHandleNode.parentNode.onchange();
328 // Make sure the hash changed.
329 assertEquals('#' + hash, document.location.hash);
331 // Make sure only the specified tab is visible.
332 var tabSwitcher = MainView.getInstance().tabSwitcher();
333 var tabIdToView = tabSwitcher.getAllTabViews();
334 for (var curTabId in tabIdToView) {
335 expectEquals(curTabId == tabId,
336 tabSwitcher.getTabView(curTabId).isVisible(),
337 curTabId + ': Unexpected visibility state.');
342 * Checks the visibility of all tab handles against expected values.
343 * @param {object.<string, bool>}: tabVisibilityState Object with a an entry
344 * for each tab's hash, and a bool indicating if it should be visible or
346 * @param {bool+}: tourTabs True if tabs expected to be visible should should
347 * each be navigated to as well.
349 NetInternalsTest.checkTabHandleVisibility = function(tabVisibilityState,
351 // The currently active tab should have a handle that is visible.
352 expectTrue(NetInternalsTest.tabHandleIsVisible(
353 NetInternalsTest.getActiveTabId()));
355 // Check visibility state of all tabs.
357 for (var hash in tabVisibilityState) {
358 var tabId = NetInternalsTest.getTabId(hash);
359 assertEquals('object', typeof NetInternalsTest.getTab(tabId),
360 'Invalid tab: ' + tabId);
361 expectEquals(tabVisibilityState[hash],
362 NetInternalsTest.tabHandleIsVisible(tabId),
363 tabId + ' visibility state is unexpected.');
364 if (tourTabs && tabVisibilityState[hash])
365 NetInternalsTest.switchToView(hash);
369 // Check that every tab was listed.
370 var tabSwitcher = MainView.getInstance().tabSwitcher();
371 var tabIdToView = tabSwitcher.getAllTabViews();
372 var expectedTabCount = 0;
373 for (tabId in tabIdToView)
375 expectEquals(tabCount, expectedTabCount);
379 * This class allows multiple Tasks to be queued up to be run sequentially.
380 * A Task can wait for asynchronous callbacks from the browser before
381 * completing, at which point the next queued Task will be run.
382 * @param {bool}: endTestWhenDone True if testDone should be called when the
383 * final task completes.
384 * @param {NetInternalsTest}: test Test fixture passed to Tasks.
387 NetInternalsTest.TaskQueue = function(endTestWhenDone) {
388 // Test fixture for passing to tasks.
390 this.isRunning_ = false;
391 this.endTestWhenDone_ = endTestWhenDone;
394 NetInternalsTest.TaskQueue.prototype = {
396 * Adds a Task to the end of the queue. Each Task may only be added once
397 * to a single queue. Also passes the text fixture to the Task.
398 * @param {Task}: task The Task to add.
400 addTask: function(task) {
401 this.tasks_.push(task);
402 task.setTaskQueue_(this);
406 * Adds a Task to the end of the queue. The task will call the provided
407 * function, and then complete.
408 * @param {function}: taskFunction The function the task will call.
410 addFunctionTask: function(taskFunction) {
411 this.addTask(new NetInternalsTest.CallFunctionTask(taskFunction));
415 * Starts running the Tasks in the queue, passing its arguments, if any,
416 * to the first Task's start function. May only be called once.
419 assertFalse(this.isRunning_);
420 this.isRunning_ = true;
421 this.runNextTask_(Array.prototype.slice.call(arguments));
425 * If there are any Tasks in |tasks_|, removes the first one and runs it.
426 * Otherwise, sets |isRunning_| to false. If |endTestWhenDone_| is true,
428 * @param {array} argArray arguments to be passed on to next Task's start
429 * method. May be a 0-length array.
431 runNextTask_: function(argArray) {
432 assertTrue(this.isRunning_);
433 // The last Task may have used |NetInternalsTest.callback|. Make sure
435 expectEquals(null, NetInternalsTest.callback);
437 if (this.tasks_.length > 0) {
438 var nextTask = this.tasks_.shift();
439 nextTask.start.apply(nextTask, argArray);
441 this.isRunning_ = false;
442 if (this.endTestWhenDone_)
449 * A Task that can be added to a TaskQueue. A Task is started with a call to
450 * the start function, and must call its own onTaskDone when complete.
453 NetInternalsTest.Task = function() {
454 this.taskQueue_ = null;
455 this.isDone_ = false;
456 this.completeAsync_ = false;
459 NetInternalsTest.Task.prototype = {
461 * Starts running the Task. Only called once per Task, must be overridden.
462 * Any arguments passed to the last Task's onTaskDone, or to run (If the
463 * first task) will be passed along.
466 assertNotReached('Start function not overridden.');
470 * Updates value of |completeAsync_|. If set to true, the next Task will
471 * start asynchronously. Useful if the Task completes on an event that
472 * the next Task may care about.
474 setCompleteAsync: function(value) {
475 this.completeAsync_ = value;
479 * @return {bool} True if this task has completed by calling onTaskDone.
486 * Sets the TaskQueue used by the task in the onTaskDone function. May only
487 * be called by the TaskQueue.
488 * @param {TaskQueue}: taskQueue The TaskQueue |this| has been added to.
490 setTaskQueue_: function(taskQueue) {
491 assertEquals(null, this.taskQueue_);
492 this.taskQueue_ = taskQueue;
496 * Must be called when a task is complete, and can only be called once for a
497 * task. Runs the next task, if any, passing along all arguments.
499 onTaskDone: function() {
500 assertFalse(this.isDone_);
503 // Function to run the next task in the queue.
504 var runNextTask = this.taskQueue_.runNextTask_.bind(
506 Array.prototype.slice.call(arguments));
508 // If we need to start the next task asynchronously, we need to wrap
509 // it with the test framework code.
510 if (this.completeAsync_) {
511 window.setTimeout(activeTest_.continueTest(WhenTestDone.EXPECT,
517 // Otherwise, just run the next task directly.
523 * A Task that can be added to a TaskQueue. A Task is started with a call to
524 * the start function, and must call its own onTaskDone when complete.
525 * @extends {NetInternalsTest.Task}
528 NetInternalsTest.CallFunctionTask = function(taskFunction) {
529 NetInternalsTest.Task.call(this);
530 assertEquals('function', typeof taskFunction);
531 this.taskFunction_ = taskFunction;
534 NetInternalsTest.CallFunctionTask.prototype = {
535 __proto__: NetInternalsTest.Task.prototype,
538 * Runs the function and then completes. Passes all arguments, if any,
539 * along to the function.
542 this.taskFunction_.apply(null, Array.prototype.slice.call(arguments));
548 * A Task that converts the given path into a URL served by the TestServer.
549 * The resulting URL will be passed to the next task. Will also start the
550 * TestServer, if needed.
551 * @param {string} path Path to convert to a URL.
552 * @extends {NetInternalsTest.Task}
555 NetInternalsTest.GetTestServerURLTask = function(path) {
556 NetInternalsTest.Task.call(this);
557 assertEquals('string', typeof path);
561 NetInternalsTest.GetTestServerURLTask.prototype = {
562 __proto__: NetInternalsTest.Task.prototype,
565 * Sets |NetInternals.callback|, and sends the path to the browser process.
568 NetInternalsTest.setCallback(this.onURLReceived_.bind(this));
569 chrome.send('getTestServerURL', [this.path_]);
573 * Completes the Task, passing the url on to the next Task.
574 * @param {string} url TestServer URL of the input path.
576 onURLReceived_: function(url) {
577 assertEquals('string', typeof url);
578 this.onTaskDone(url);
583 * A Task that creates an incognito window and only completes once it has
584 * navigated to about:blank. The waiting is required to avoid reentrancy
585 * issues, since the function to create the incognito browser also waits
586 * for the navigation to complete. May not be called if there's already an
587 * incognito browser in existence.
588 * @extends {NetInternalsTest.Task}
591 NetInternalsTest.CreateIncognitoBrowserTask = function() {
592 NetInternalsTest.Task.call(this);
595 NetInternalsTest.CreateIncognitoBrowserTask.prototype = {
596 __proto__: NetInternalsTest.Task.prototype,
599 * Tells the browser process to create an incognito browser, and sets
600 * up a callback to be called on completion.
603 // Reuse the BrowserBridge's callback mechanism, since it's already
604 // wrapped in our test harness.
605 assertEquals('undefined',
606 typeof g_browser.onIncognitoBrowserCreatedForTest);
607 g_browser.onIncognitoBrowserCreatedForTest =
608 this.onIncognitoBrowserCreatedForTest.bind(this);
610 chrome.send('createIncognitoBrowser');
614 * Deletes the callback function, and completes the task.
616 onIncognitoBrowserCreatedForTest: function() {
617 delete g_browser.onIncognitoBrowserCreatedForTest;
623 * Returns a task that closes an incognito window created with the task
624 * above. May only be called if there's an incognito window created by
625 * the above function that has yet to be closed. Returns immediately.
626 * @return {Task} Task that closes incognito browser window.
628 NetInternalsTest.getCloseIncognitoBrowserTask = function() {
629 return new NetInternalsTest.CallFunctionTask(
631 chrome.send('closeIncognitoBrowser');
636 * Returns true if a node does not have a 'display' property of 'none'.
637 * @param {node}: node The node to check.
639 NetInternalsTest.isDisplayed = function(node) {
640 var style = getComputedStyle(node);
641 return style.getPropertyValue('display') != 'none';
645 * Creates a new NetLog source. Note that the id may conflict with events
646 * received from the browser.
647 * @param {int}: type The source type.
648 * @param {int}: id The source id.
651 NetInternalsTest.Source = function(type, id) {
652 assertNotEquals(getKeyWithValue(EventSourceType, type), '?');
659 * Creates a new NetLog event.
660 * @param {Source}: source The source associated with the event.
661 * @param {int}: type The event id.
662 * @param {int}: time When the event occurred.
663 * @param {int}: phase The event phase.
664 * @param {object}: params The event parameters. May be null.
667 NetInternalsTest.Event = function(source, type, time, phase, params) {
668 assertNotEquals(getKeyWithValue(EventType, type), '?');
669 assertNotEquals(getKeyWithValue(EventPhase, phase), '?');
671 this.source = source;
674 this.time = '' + time;
677 this.params = params;
681 * Creates a new NetLog begin event. Parameters are the same as Event,
682 * except there's no |phase| argument.
685 NetInternalsTest.createBeginEvent = function(source, type, time, params) {
686 return new NetInternalsTest.Event(source, type, time,
687 EventPhase.PHASE_BEGIN, params);
691 * Creates a new NetLog end event. Parameters are the same as Event,
692 * except there's no |phase| argument.
695 NetInternalsTest.createEndEvent = function(source, type, time, params) {
696 return new NetInternalsTest.Event(source, type, time,
697 EventPhase.PHASE_END, params);
701 * Creates a new NetLog end event matching the given begin event.
702 * @param {Event}: beginEvent The begin event. Returned event will have the
703 * same source and type.
704 * @param {int}: time When the event occurred.
705 * @param {object}: params The event parameters. May be null.
708 NetInternalsTest.createMatchingEndEvent = function(beginEvent, time, params) {
709 return NetInternalsTest.createEndEvent(
710 beginEvent.source, beginEvent.type, time, params);
714 * Checks that only the given status view node is visible.
715 * @param {string}: nodeId ID of the node that should be visible.
717 NetInternalsTest.expectStatusViewNodeVisible = function(nodeId) {
719 CaptureStatusView.MAIN_BOX_ID,
720 LoadedStatusView.MAIN_BOX_ID,
721 HaltedStatusView.MAIN_BOX_ID
724 for (var i = 0; i < allIds.length; ++i) {
725 var curId = allIds[i];
726 expectEquals(nodeId == curId, NetInternalsTest.nodeIsVisible($(curId)));
730 return NetInternalsTest;