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 spdy
: SpdyView
.TAB_ID
,
296 quic
: QuicView
.TAB_ID
,
297 httpCache
: HttpCacheView
.TAB_ID
,
298 modules
: ModulesView
.TAB_ID
,
299 tests
: TestView
.TAB_ID
,
300 hsts
: HSTSView
.TAB_ID
,
301 logs
: LogsView
.TAB_ID
,
302 prerender
: PrerenderView
.TAB_ID
,
303 bandwidth
: BandwidthView
.TAB_ID
,
304 chromeos
: CrosView
.TAB_ID
,
305 visualizer
: CrosLogVisualizerView
.TAB_ID
308 assertEquals(typeof hashToTabHandleIdMap
[hash
], 'string',
309 'Invalid tab anchor: ' + hash
);
310 var tabId
= hashToTabHandleIdMap
[hash
];
311 assertEquals('object', typeof NetInternalsTest
.getTab(tabId
),
312 'Invalid tab: ' + tabId
);
317 * Switches to the specified tab.
318 * @param {string}: hash Hash associated with the tab to switch to.
320 NetInternalsTest
.switchToView = function(hash
) {
321 var tabId
= NetInternalsTest
.getTabId(hash
);
323 // Make sure the tab handle is visible, as we only simulate normal usage.
324 expectTrue(NetInternalsTest
.tabHandleIsVisible(tabId
),
325 tabId
+ ' does not have a visible tab handle.');
326 var tabHandleNode
= NetInternalsTest
.getTab(tabId
).menuItem
;
328 // Simulate selecting the menuitem.
329 tabHandleNode
.selected
= true;
330 tabHandleNode
.parentNode
.onchange();
332 // Make sure the hash changed.
333 assertEquals('#' + hash
, document
.location
.hash
);
335 // Make sure only the specified tab is visible.
336 var tabSwitcher
= MainView
.getInstance().tabSwitcher();
337 var tabIdToView
= tabSwitcher
.getAllTabViews();
338 for (var curTabId
in tabIdToView
) {
339 expectEquals(curTabId
== tabId
,
340 tabSwitcher
.getTabView(curTabId
).isVisible(),
341 curTabId
+ ': Unexpected visibility state.');
346 * Checks the visibility of all tab handles against expected values.
347 * @param {object.<string, bool>}: tabVisibilityState Object with a an entry
348 * for each tab's hash, and a bool indicating if it should be visible or
350 * @param {bool+}: tourTabs True if tabs expected to be visible should should
351 * each be navigated to as well.
353 NetInternalsTest
.checkTabHandleVisibility = function(tabVisibilityState
,
355 // The currently active tab should have a handle that is visible.
356 expectTrue(NetInternalsTest
.tabHandleIsVisible(
357 NetInternalsTest
.getActiveTabId()));
359 // Check visibility state of all tabs.
361 for (var hash
in tabVisibilityState
) {
362 var tabId
= NetInternalsTest
.getTabId(hash
);
363 assertEquals('object', typeof NetInternalsTest
.getTab(tabId
),
364 'Invalid tab: ' + tabId
);
365 expectEquals(tabVisibilityState
[hash
],
366 NetInternalsTest
.tabHandleIsVisible(tabId
),
367 tabId
+ ' visibility state is unexpected.');
368 if (tourTabs
&& tabVisibilityState
[hash
])
369 NetInternalsTest
.switchToView(hash
);
373 // Check that every tab was listed.
374 var tabSwitcher
= MainView
.getInstance().tabSwitcher();
375 var tabIdToView
= tabSwitcher
.getAllTabViews();
376 var expectedTabCount
= 0;
377 for (tabId
in tabIdToView
)
379 expectEquals(tabCount
, expectedTabCount
);
383 * This class allows multiple Tasks to be queued up to be run sequentially.
384 * A Task can wait for asynchronous callbacks from the browser before
385 * completing, at which point the next queued Task will be run.
386 * @param {bool}: endTestWhenDone True if testDone should be called when the
387 * final task completes.
388 * @param {NetInternalsTest}: test Test fixture passed to Tasks.
391 NetInternalsTest
.TaskQueue = function(endTestWhenDone
) {
392 // Test fixture for passing to tasks.
394 this.isRunning_
= false;
395 this.endTestWhenDone_
= endTestWhenDone
;
398 NetInternalsTest
.TaskQueue
.prototype = {
400 * Adds a Task to the end of the queue. Each Task may only be added once
401 * to a single queue. Also passes the text fixture to the Task.
402 * @param {Task}: task The Task to add.
404 addTask: function(task
) {
405 this.tasks_
.push(task
);
406 task
.setTaskQueue_(this);
410 * Adds a Task to the end of the queue. The task will call the provided
411 * function, and then complete.
412 * @param {function}: taskFunction The function the task will call.
414 addFunctionTask: function(taskFunction
) {
415 this.addTask(new NetInternalsTest
.CallFunctionTask(taskFunction
));
419 * Starts running the Tasks in the queue, passing its arguments, if any,
420 * to the first Task's start function. May only be called once.
423 assertFalse(this.isRunning_
);
424 this.isRunning_
= true;
425 this.runNextTask_(Array
.prototype.slice
.call(arguments
));
429 * If there are any Tasks in |tasks_|, removes the first one and runs it.
430 * Otherwise, sets |isRunning_| to false. If |endTestWhenDone_| is true,
432 * @param {array} argArray arguments to be passed on to next Task's start
433 * method. May be a 0-length array.
435 runNextTask_: function(argArray
) {
436 assertTrue(this.isRunning_
);
437 // The last Task may have used |NetInternalsTest.callback|. Make sure
439 expectEquals(null, NetInternalsTest
.callback
);
441 if (this.tasks_
.length
> 0) {
442 var nextTask
= this.tasks_
.shift();
443 nextTask
.start
.apply(nextTask
, argArray
);
445 this.isRunning_
= false;
446 if (this.endTestWhenDone_
)
453 * A Task that can be added to a TaskQueue. A Task is started with a call to
454 * the start function, and must call its own onTaskDone when complete.
457 NetInternalsTest
.Task = function() {
458 this.taskQueue_
= null;
459 this.isDone_
= false;
460 this.completeAsync_
= false;
463 NetInternalsTest
.Task
.prototype = {
465 * Starts running the Task. Only called once per Task, must be overridden.
466 * Any arguments passed to the last Task's onTaskDone, or to run (If the
467 * first task) will be passed along.
470 assertNotReached('Start function not overridden.');
474 * Updates value of |completeAsync_|. If set to true, the next Task will
475 * start asynchronously. Useful if the Task completes on an event that
476 * the next Task may care about.
478 setCompleteAsync: function(value
) {
479 this.completeAsync_
= value
;
483 * @return {bool} True if this task has completed by calling onTaskDone.
490 * Sets the TaskQueue used by the task in the onTaskDone function. May only
491 * be called by the TaskQueue.
492 * @param {TaskQueue}: taskQueue The TaskQueue |this| has been added to.
494 setTaskQueue_: function(taskQueue
) {
495 assertEquals(null, this.taskQueue_
);
496 this.taskQueue_
= taskQueue
;
500 * Must be called when a task is complete, and can only be called once for a
501 * task. Runs the next task, if any, passing along all arguments.
503 onTaskDone: function() {
504 assertFalse(this.isDone_
);
507 // Function to run the next task in the queue.
508 var runNextTask
= this.taskQueue_
.runNextTask_
.bind(
510 Array
.prototype.slice
.call(arguments
));
512 // If we need to start the next task asynchronously, we need to wrap
513 // it with the test framework code.
514 if (this.completeAsync_
) {
515 window
.setTimeout(activeTest_
.continueTest(WhenTestDone
.EXPECT
,
521 // Otherwise, just run the next task directly.
527 * A Task that can be added to a TaskQueue. A Task is started with a call to
528 * the start function, and must call its own onTaskDone when complete.
529 * @extends {NetInternalsTest.Task}
532 NetInternalsTest
.CallFunctionTask = function(taskFunction
) {
533 NetInternalsTest
.Task
.call(this);
534 assertEquals('function', typeof taskFunction
);
535 this.taskFunction_
= taskFunction
;
538 NetInternalsTest
.CallFunctionTask
.prototype = {
539 __proto__
: NetInternalsTest
.Task
.prototype,
542 * Runs the function and then completes. Passes all arguments, if any,
543 * along to the function.
546 this.taskFunction_
.apply(null, Array
.prototype.slice
.call(arguments
));
552 * A Task that converts the given path into a URL served by the TestServer.
553 * The resulting URL will be passed to the next task. Will also start the
554 * TestServer, if needed.
555 * @param {string} path Path to convert to a URL.
556 * @extends {NetInternalsTest.Task}
559 NetInternalsTest
.GetTestServerURLTask = function(path
) {
560 NetInternalsTest
.Task
.call(this);
561 assertEquals('string', typeof path
);
565 NetInternalsTest
.GetTestServerURLTask
.prototype = {
566 __proto__
: NetInternalsTest
.Task
.prototype,
569 * Sets |NetInternals.callback|, and sends the path to the browser process.
572 NetInternalsTest
.setCallback(this.onURLReceived_
.bind(this));
573 chrome
.send('getTestServerURL', [this.path_
]);
577 * Completes the Task, passing the url on to the next Task.
578 * @param {string} url TestServer URL of the input path.
580 onURLReceived_: function(url
) {
581 assertEquals('string', typeof url
);
582 this.onTaskDone(url
);
587 * A Task that creates an incognito window and only completes once it has
588 * navigated to about:blank. The waiting is required to avoid reentrancy
589 * issues, since the function to create the incognito browser also waits
590 * for the navigation to complete. May not be called if there's already an
591 * incognito browser in existence.
592 * @extends {NetInternalsTest.Task}
595 NetInternalsTest
.CreateIncognitoBrowserTask = function() {
596 NetInternalsTest
.Task
.call(this);
599 NetInternalsTest
.CreateIncognitoBrowserTask
.prototype = {
600 __proto__
: NetInternalsTest
.Task
.prototype,
603 * Tells the browser process to create an incognito browser, and sets
604 * up a callback to be called on completion.
607 // Reuse the BrowserBridge's callback mechanism, since it's already
608 // wrapped in our test harness.
609 assertEquals('undefined',
610 typeof g_browser
.onIncognitoBrowserCreatedForTest
);
611 g_browser
.onIncognitoBrowserCreatedForTest
=
612 this.onIncognitoBrowserCreatedForTest
.bind(this);
614 chrome
.send('createIncognitoBrowser');
618 * Deletes the callback function, and completes the task.
620 onIncognitoBrowserCreatedForTest: function() {
621 delete g_browser
.onIncognitoBrowserCreatedForTest
;
627 * Returns a task that closes an incognito window created with the task
628 * above. May only be called if there's an incognito window created by
629 * the above function that has yet to be closed. Returns immediately.
630 * @return {Task} Task that closes incognito browser window.
632 NetInternalsTest
.getCloseIncognitoBrowserTask = function() {
633 return new NetInternalsTest
.CallFunctionTask(
635 chrome
.send('closeIncognitoBrowser');
640 * Returns true if a node does not have a 'display' property of 'none'.
641 * @param {node}: node The node to check.
643 NetInternalsTest
.isDisplayed = function(node
) {
644 var style
= getComputedStyle(node
);
645 return style
.getPropertyValue('display') != 'none';
649 * Creates a new NetLog source. Note that the id may conflict with events
650 * received from the browser.
651 * @param {int}: type The source type.
652 * @param {int}: id The source id.
655 NetInternalsTest
.Source = function(type
, id
) {
656 assertNotEquals(getKeyWithValue(EventSourceType
, type
), '?');
663 * Creates a new NetLog event.
664 * @param {Source}: source The source associated with the event.
665 * @param {int}: type The event id.
666 * @param {int}: time When the event occurred.
667 * @param {int}: phase The event phase.
668 * @param {object}: params The event parameters. May be null.
671 NetInternalsTest
.Event = function(source
, type
, time
, phase
, params
) {
672 assertNotEquals(getKeyWithValue(EventType
, type
), '?');
673 assertNotEquals(getKeyWithValue(EventPhase
, phase
), '?');
675 this.source
= source
;
678 this.time
= '' + time
;
681 this.params
= params
;
685 * Creates a new NetLog begin event. Parameters are the same as Event,
686 * except there's no |phase| argument.
689 NetInternalsTest
.createBeginEvent = function(source
, type
, time
, params
) {
690 return new NetInternalsTest
.Event(source
, type
, time
,
691 EventPhase
.PHASE_BEGIN
, params
);
695 * Creates a new NetLog end event. Parameters are the same as Event,
696 * except there's no |phase| argument.
699 NetInternalsTest
.createEndEvent = function(source
, type
, time
, params
) {
700 return new NetInternalsTest
.Event(source
, type
, time
,
701 EventPhase
.PHASE_END
, params
);
705 * Creates a new NetLog end event matching the given begin event.
706 * @param {Event}: beginEvent The begin event. Returned event will have the
707 * same source and type.
708 * @param {int}: time When the event occurred.
709 * @param {object}: params The event parameters. May be null.
712 NetInternalsTest
.createMatchingEndEvent = function(beginEvent
, time
, params
) {
713 return NetInternalsTest
.createEndEvent(
714 beginEvent
.source
, beginEvent
.type
, time
, params
);
718 * Checks that only the given status view node is visible.
719 * @param {string}: nodeId ID of the node that should be visible.
721 NetInternalsTest
.expectStatusViewNodeVisible = function(nodeId
) {
723 CaptureStatusView
.MAIN_BOX_ID
,
724 LoadedStatusView
.MAIN_BOX_ID
,
725 HaltedStatusView
.MAIN_BOX_ID
728 for (var i
= 0; i
< allIds
.length
; ++i
) {
729 var curId
= allIds
[i
];
730 expectEquals(nodeId
== curId
, NetInternalsTest
.nodeIsVisible($(curId
)));
734 return NetInternalsTest
;