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 // TODO(aboxhall): enable when this bug is fixed:
78 // https://github.com/GoogleChrome/accessibility-developer-tools/issues/69
79 this.accessibilityAuditConfig.auditRulesToIgnore.push(
80 'focusableElementNotVisibleAndNotAriaHidden');
82 // Wrap g_browser.receive around a test function so that assert and expect
83 // functions can be called from observers.
85 this.continueTest(WhenTestDone.EXPECT,
86 BrowserBridge.prototype.receive.bind(g_browser));
88 g_browser.setPollInterval(TESTING_POLL_INTERVAL_MS);
90 var runTest = this.deferRunTest(WhenTestDone.EXPECT);
92 // If we've already received the constants, start the tests.
94 // Stat test asynchronously, to avoid running a nested test function.
95 window.setTimeout(runTest, 0);
99 // Otherwise, wait until we do.
100 console.log('Received constants late.');
103 * Observer that starts the tests once we've received the constants.
105 function ConstantsObserver() {
106 this.testStarted_ = false;
109 ConstantsObserver.prototype.onReceivedConstants = function() {
110 if (!this.testStarted_) {
111 this.testStarted_ = true;
112 // Stat test asynchronously, to avoid running a nested test function,
113 // and so we don't call any constants observers used by individual
115 window.setTimeout(runTest, 0);
119 g_browser.addConstantsObserver(new ConstantsObserver());
124 * A callback function for use by asynchronous Tasks that need a return value
125 * from the NetInternalsTest::MessageHandler. Must be null when no such
126 * Task is running. Set by NetInternalsTest.setCallback. Automatically
127 * cleared once called. Must only be called through
128 * NetInternalsTest::MessageHandler::RunCallback, from the browser process.
130 NetInternalsTest.callback = null;
133 * Sets NetInternalsTest.callback. Any arguments will be passed to the
135 * @param {function} callbackFunction Callback function to be called from the
138 NetInternalsTest.setCallback = function(callbackFunction) {
139 // Make sure no Task has already set the callback function.
140 assertEquals(null, NetInternalsTest.callback);
142 // Wrap |callbackFunction| in a function that clears
143 // |NetInternalsTest.callback| before calling |callbackFunction|.
144 var callbackFunctionWrapper = function() {
145 NetInternalsTest.callback = null;
146 callbackFunction.apply(null, Array.prototype.slice.call(arguments));
149 // Wrap |callbackFunctionWrapper| with test framework code.
150 NetInternalsTest.callback =
151 activeTest_.continueTest(WhenTestDone.EXPECT, callbackFunctionWrapper);
155 * Returns the first tbody that's a descendant of |ancestorId|. If the
156 * specified node is itself a table body node, just returns that node.
157 * Returns null if no such node is found.
158 * @param {string} ancestorId ID of an HTML element with a tbody descendant.
159 * @return {node} The tbody node, or null.
161 NetInternalsTest.getTbodyDescendent = function(ancestorId) {
162 if ($(ancestorId).nodeName == 'TBODY')
163 return $(ancestorId);
164 // The tbody element of the first styled table in |parentId|.
165 return document.querySelector('#' + ancestorId + ' tbody');
169 * Finds the first tbody that's a descendant of |ancestorId|, including the
170 * |ancestorId| element itself, and returns the number of rows it has.
171 * Returns -1 if there's no such table. Excludes hidden rows.
172 * @param {string} ancestorId ID of an HTML element with a tbody descendant.
173 * @return {number} Number of rows the style table's body has.
175 NetInternalsTest.getTbodyNumRows = function(ancestorId) {
176 // The tbody element of the first styled table in |parentId|.
177 var tbody = NetInternalsTest.getTbodyDescendent(ancestorId);
180 var visibleChildren = 0;
181 for (var i = 0; i < tbody.children.length; ++i) {
182 if (NetInternalsTest.nodeIsVisible(tbody.children[i]))
185 return visibleChildren;
189 * Finds the first tbody that's a descendant of |ancestorId|, including the
190 * |ancestorId| element itself, and checks if it has exactly |expectedRows|
191 * rows. Does not count hidden rows.
192 * @param {string} ancestorId ID of an HTML element with a tbody descendant.
193 * @param {number} expectedRows Expected number of rows in the table.
195 NetInternalsTest.checkTbodyRows = function(ancestorId, expectedRows) {
196 expectEquals(expectedRows,
197 NetInternalsTest.getTbodyNumRows(ancestorId),
198 'Incorrect number of rows in ' + ancestorId);
202 * Finds the tbody that's a descendant of |ancestorId|, including the
203 * |ancestorId| element itself, and returns the text of the specified cell.
204 * If the cell does not exist, throws an exception. Skips over hidden rows.
205 * @param {string} ancestorId ID of an HTML element with a tbody descendant.
206 * @param {number} row Row of the value to retrieve.
207 * @param {number} column Column of the value to retrieve.
209 NetInternalsTest.getTbodyText = function(ancestorId, row, column) {
210 var tbody = NetInternalsTest.getTbodyDescendent(ancestorId);
211 var currentChild = tbody.children[0];
212 while (currentChild) {
213 if (NetInternalsTest.nodeIsVisible(currentChild)) {
215 return currentChild.children[column].innerText;
218 currentChild = currentChild.nextElementSibling;
220 return 'invalid row';
224 * Returns the view and menu item node for the tab with given id.
225 * Asserts if the tab can't be found.
226 * @param {string}: tabId Id of the tab to lookup.
229 NetInternalsTest.getTab = function(tabId) {
230 var tabSwitcher = MainView.getInstance().tabSwitcher();
231 var view = tabSwitcher.getTabView(tabId);
232 var menuItem = tabSwitcher.getMenuItemNode_(tabId);
234 assertNotEquals(view, undefined, tabId + ' does not exist.');
235 assertNotEquals(menuItem, undefined, tabId + ' does not exist.');
244 * Returns true if the node is visible.
245 * @param {Node}: node Node to check the visibility state of.
246 * @return {bool} Whether or not the node is visible.
248 NetInternalsTest.nodeIsVisible = function(node) {
249 return node.style.display != 'none';
253 * Returns true if the specified tab's handle is visible, false otherwise.
254 * Asserts if the handle can't be found.
255 * @param {string}: tabId Id of the tab to check.
256 * @return {bool} Whether or not the tab's handle is visible.
258 NetInternalsTest.tabHandleIsVisible = function(tabId) {
259 var tabHandleNode = NetInternalsTest.getTab(tabId).menuItem;
260 return NetInternalsTest.nodeIsVisible(tabHandleNode);
264 * Returns the id of the currently active tab.
265 * @return {string} ID of the active tab.
267 NetInternalsTest.getActiveTabId = function() {
268 return MainView.getInstance().tabSwitcher().getActiveTabId();
272 * Returns the tab id of a tab, given its associated URL hash value. Asserts
273 * if |hash| has no associated tab.
274 * @param {string}: hash Hash associated with the tab to return the id of.
275 * @return {string} String identifier of the tab with the given hash.
277 NetInternalsTest.getTabId = function(hash) {
279 * Map of tab handle names to location hashes. Since the text fixture
280 * must be runnable independent of net-internals, for generating the
281 * test's cc files, must be careful to only create this map while a test
283 * @type {object.<string, string>}
285 var hashToTabHandleIdMap = {
286 capture: CaptureView.TAB_ID,
287 export: ExportView.TAB_ID,
288 import: ImportView.TAB_ID,
289 proxy: ProxyView.TAB_ID,
290 events: EventsView.TAB_ID,
291 waterfall: WaterfallView.TAB_ID,
292 timeline: TimelineView.TAB_ID,
294 sockets: SocketsView.TAB_ID,
295 http2: SpdyView.TAB_ID,
296 quic: QuicView.TAB_ID,
297 sdch: SdchView.TAB_ID,
298 httpCache: HttpCacheView.TAB_ID,
299 modules: ModulesView.TAB_ID,
300 hsts: HSTSView.TAB_ID,
301 prerender: PrerenderView.TAB_ID,
302 bandwidth: BandwidthView.TAB_ID,
303 chromeos: CrosView.TAB_ID
306 assertEquals(typeof hashToTabHandleIdMap[hash], 'string',
307 'Invalid tab anchor: ' + hash);
308 var tabId = hashToTabHandleIdMap[hash];
309 assertEquals('object', typeof NetInternalsTest.getTab(tabId),
310 'Invalid tab: ' + tabId);
315 * Switches to the specified tab.
316 * @param {string}: hash Hash associated with the tab to switch to.
318 NetInternalsTest.switchToView = function(hash) {
319 var tabId = NetInternalsTest.getTabId(hash);
321 // Make sure the tab handle is visible, as we only simulate normal usage.
322 expectTrue(NetInternalsTest.tabHandleIsVisible(tabId),
323 tabId + ' does not have a visible tab handle.');
324 var tabHandleNode = NetInternalsTest.getTab(tabId).menuItem;
326 // Simulate selecting the menuitem.
327 tabHandleNode.selected = true;
328 tabHandleNode.parentNode.onchange();
330 // Make sure the hash changed.
331 assertEquals('#' + hash, document.location.hash);
333 // Make sure only the specified tab is visible.
334 var tabSwitcher = MainView.getInstance().tabSwitcher();
335 var tabIdToView = tabSwitcher.getAllTabViews();
336 for (var curTabId in tabIdToView) {
337 expectEquals(curTabId == tabId,
338 tabSwitcher.getTabView(curTabId).isVisible(),
339 curTabId + ': Unexpected visibility state.');
344 * Checks the visibility of all tab handles against expected values.
345 * @param {object.<string, bool>}: tabVisibilityState Object with a an entry
346 * for each tab's hash, and a bool indicating if it should be visible or
348 * @param {bool+}: tourTabs True if tabs expected to be visible should should
349 * each be navigated to as well.
351 NetInternalsTest.checkTabHandleVisibility = function(tabVisibilityState,
353 // The currently active tab should have a handle that is visible.
354 expectTrue(NetInternalsTest.tabHandleIsVisible(
355 NetInternalsTest.getActiveTabId()));
357 // Check visibility state of all tabs.
359 for (var hash in tabVisibilityState) {
360 var tabId = NetInternalsTest.getTabId(hash);
361 assertEquals('object', typeof NetInternalsTest.getTab(tabId),
362 'Invalid tab: ' + tabId);
363 expectEquals(tabVisibilityState[hash],
364 NetInternalsTest.tabHandleIsVisible(tabId),
365 tabId + ' visibility state is unexpected.');
366 if (tourTabs && tabVisibilityState[hash])
367 NetInternalsTest.switchToView(hash);
371 // Check that every tab was listed.
372 var tabSwitcher = MainView.getInstance().tabSwitcher();
373 var tabIdToView = tabSwitcher.getAllTabViews();
374 var expectedTabCount = 0;
375 for (tabId in tabIdToView)
377 expectEquals(tabCount, expectedTabCount);
381 * This class allows multiple Tasks to be queued up to be run sequentially.
382 * A Task can wait for asynchronous callbacks from the browser before
383 * completing, at which point the next queued Task will be run.
384 * @param {bool}: endTestWhenDone True if testDone should be called when the
385 * final task completes.
386 * @param {NetInternalsTest}: test Test fixture passed to Tasks.
389 NetInternalsTest.TaskQueue = function(endTestWhenDone) {
390 // Test fixture for passing to tasks.
392 this.isRunning_ = false;
393 this.endTestWhenDone_ = endTestWhenDone;
396 NetInternalsTest.TaskQueue.prototype = {
398 * Adds a Task to the end of the queue. Each Task may only be added once
399 * to a single queue. Also passes the text fixture to the Task.
400 * @param {Task}: task The Task to add.
402 addTask: function(task) {
403 this.tasks_.push(task);
404 task.setTaskQueue_(this);
408 * Adds a Task to the end of the queue. The task will call the provided
409 * function, and then complete.
410 * @param {function}: taskFunction The function the task will call.
412 addFunctionTask: function(taskFunction) {
413 this.addTask(new NetInternalsTest.CallFunctionTask(taskFunction));
417 * Starts running the Tasks in the queue, passing its arguments, if any,
418 * to the first Task's start function. May only be called once.
421 assertFalse(this.isRunning_);
422 this.isRunning_ = true;
423 this.runNextTask_(Array.prototype.slice.call(arguments));
427 * If there are any Tasks in |tasks_|, removes the first one and runs it.
428 * Otherwise, sets |isRunning_| to false. If |endTestWhenDone_| is true,
430 * @param {array} argArray arguments to be passed on to next Task's start
431 * method. May be a 0-length array.
433 runNextTask_: function(argArray) {
434 assertTrue(this.isRunning_);
435 // The last Task may have used |NetInternalsTest.callback|. Make sure
437 expectEquals(null, NetInternalsTest.callback);
439 if (this.tasks_.length > 0) {
440 var nextTask = this.tasks_.shift();
441 nextTask.start.apply(nextTask, argArray);
443 this.isRunning_ = false;
444 if (this.endTestWhenDone_)
451 * A Task that can be added to a TaskQueue. A Task is started with a call to
452 * the start function, and must call its own onTaskDone when complete.
455 NetInternalsTest.Task = function() {
456 this.taskQueue_ = null;
457 this.isDone_ = false;
458 this.completeAsync_ = false;
461 NetInternalsTest.Task.prototype = {
463 * Starts running the Task. Only called once per Task, must be overridden.
464 * Any arguments passed to the last Task's onTaskDone, or to run (If the
465 * first task) will be passed along.
468 assertNotReached('Start function not overridden.');
472 * Updates value of |completeAsync_|. If set to true, the next Task will
473 * start asynchronously. Useful if the Task completes on an event that
474 * the next Task may care about.
476 setCompleteAsync: function(value) {
477 this.completeAsync_ = value;
481 * @return {bool} True if this task has completed by calling onTaskDone.
488 * Sets the TaskQueue used by the task in the onTaskDone function. May only
489 * be called by the TaskQueue.
490 * @param {TaskQueue}: taskQueue The TaskQueue |this| has been added to.
492 setTaskQueue_: function(taskQueue) {
493 assertEquals(null, this.taskQueue_);
494 this.taskQueue_ = taskQueue;
498 * Must be called when a task is complete, and can only be called once for a
499 * task. Runs the next task, if any, passing along all arguments.
501 onTaskDone: function() {
502 assertFalse(this.isDone_);
505 // Function to run the next task in the queue.
506 var runNextTask = this.taskQueue_.runNextTask_.bind(
508 Array.prototype.slice.call(arguments));
510 // If we need to start the next task asynchronously, we need to wrap
511 // it with the test framework code.
512 if (this.completeAsync_) {
513 window.setTimeout(activeTest_.continueTest(WhenTestDone.EXPECT,
519 // Otherwise, just run the next task directly.
525 * A Task that can be added to a TaskQueue. A Task is started with a call to
526 * the start function, and must call its own onTaskDone when complete.
527 * @extends {NetInternalsTest.Task}
530 NetInternalsTest.CallFunctionTask = function(taskFunction) {
531 NetInternalsTest.Task.call(this);
532 assertEquals('function', typeof taskFunction);
533 this.taskFunction_ = taskFunction;
536 NetInternalsTest.CallFunctionTask.prototype = {
537 __proto__: NetInternalsTest.Task.prototype,
540 * Runs the function and then completes. Passes all arguments, if any,
541 * along to the function.
544 this.taskFunction_.apply(null, Array.prototype.slice.call(arguments));
550 * A Task that converts the given path into a URL served by the TestServer.
551 * The resulting URL will be passed to the next task. Will also start the
552 * TestServer, if needed.
553 * @param {string} path Path to convert to a URL.
554 * @extends {NetInternalsTest.Task}
557 NetInternalsTest.GetTestServerURLTask = function(path) {
558 NetInternalsTest.Task.call(this);
559 assertEquals('string', typeof path);
563 NetInternalsTest.GetTestServerURLTask.prototype = {
564 __proto__: NetInternalsTest.Task.prototype,
567 * Sets |NetInternals.callback|, and sends the path to the browser process.
570 NetInternalsTest.setCallback(this.onURLReceived_.bind(this));
571 chrome.send('getTestServerURL', [this.path_]);
575 * Completes the Task, passing the url on to the next Task.
576 * @param {string} url TestServer URL of the input path.
578 onURLReceived_: function(url) {
579 assertEquals('string', typeof url);
580 this.onTaskDone(url);
585 * A Task that creates an incognito window and only completes once it has
586 * navigated to about:blank. The waiting is required to avoid reentrancy
587 * issues, since the function to create the incognito browser also waits
588 * for the navigation to complete. May not be called if there's already an
589 * incognito browser in existence.
590 * @extends {NetInternalsTest.Task}
593 NetInternalsTest.CreateIncognitoBrowserTask = function() {
594 NetInternalsTest.Task.call(this);
597 NetInternalsTest.CreateIncognitoBrowserTask.prototype = {
598 __proto__: NetInternalsTest.Task.prototype,
601 * Tells the browser process to create an incognito browser, and sets
602 * up a callback to be called on completion.
605 // Reuse the BrowserBridge's callback mechanism, since it's already
606 // wrapped in our test harness.
607 assertEquals('undefined',
608 typeof g_browser.onIncognitoBrowserCreatedForTest);
609 g_browser.onIncognitoBrowserCreatedForTest =
610 this.onIncognitoBrowserCreatedForTest.bind(this);
612 chrome.send('createIncognitoBrowser');
616 * Deletes the callback function, and completes the task.
618 onIncognitoBrowserCreatedForTest: function() {
619 delete g_browser.onIncognitoBrowserCreatedForTest;
625 * Returns a task that closes an incognito window created with the task
626 * above. May only be called if there's an incognito window created by
627 * the above function that has yet to be closed. Returns immediately.
628 * @return {Task} Task that closes incognito browser window.
630 NetInternalsTest.getCloseIncognitoBrowserTask = function() {
631 return new NetInternalsTest.CallFunctionTask(
633 chrome.send('closeIncognitoBrowser');
638 * Returns true if a node does not have a 'display' property of 'none'.
639 * @param {node}: node The node to check.
641 NetInternalsTest.isDisplayed = function(node) {
642 var style = getComputedStyle(node);
643 return style.getPropertyValue('display') != 'none';
647 * Creates a new NetLog source. Note that the id may conflict with events
648 * received from the browser.
649 * @param {int}: type The source type.
650 * @param {int}: id The source id.
653 NetInternalsTest.Source = function(type, id) {
654 assertNotEquals(getKeyWithValue(EventSourceType, type), '?');
661 * Creates a new NetLog event.
662 * @param {Source}: source The source associated with the event.
663 * @param {int}: type The event id.
664 * @param {int}: time When the event occurred.
665 * @param {int}: phase The event phase.
666 * @param {object}: params The event parameters. May be null.
669 NetInternalsTest.Event = function(source, type, time, phase, params) {
670 assertNotEquals(getKeyWithValue(EventType, type), '?');
671 assertNotEquals(getKeyWithValue(EventPhase, phase), '?');
673 this.source = source;
676 this.time = '' + time;
679 this.params = params;
683 * Creates a new NetLog begin event. Parameters are the same as Event,
684 * except there's no |phase| argument.
687 NetInternalsTest.createBeginEvent = function(source, type, time, params) {
688 return new NetInternalsTest.Event(source, type, time,
689 EventPhase.PHASE_BEGIN, params);
693 * Creates a new NetLog end event. Parameters are the same as Event,
694 * except there's no |phase| argument.
697 NetInternalsTest.createEndEvent = function(source, type, time, params) {
698 return new NetInternalsTest.Event(source, type, time,
699 EventPhase.PHASE_END, params);
703 * Creates a new NetLog end event matching the given begin event.
704 * @param {Event}: beginEvent The begin event. Returned event will have the
705 * same source and type.
706 * @param {int}: time When the event occurred.
707 * @param {object}: params The event parameters. May be null.
710 NetInternalsTest.createMatchingEndEvent = function(beginEvent, time, params) {
711 return NetInternalsTest.createEndEvent(
712 beginEvent.source, beginEvent.type, time, params);
716 * Checks that only the given status view node is visible.
717 * @param {string}: nodeId ID of the node that should be visible.
719 NetInternalsTest.expectStatusViewNodeVisible = function(nodeId) {
721 CaptureStatusView.MAIN_BOX_ID,
722 LoadedStatusView.MAIN_BOX_ID,
723 HaltedStatusView.MAIN_BOX_ID
726 for (var i = 0; i < allIds.length; ++i) {
727 var curId = allIds[i];
728 expectEquals(nodeId == curId, NetInternalsTest.nodeIsVisible($(curId)));
732 return NetInternalsTest;