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
;