2 * Copyright (C) 2010 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 * @fileoverview This file contains small testing framework along with the
34 * test suite for the frontend. These tests are a part of the continues build
35 * and are executed by the devtools_sanity_unittest.cc as a part of the
36 * Interactive UI Test suite.
37 * FIXME: change field naming style to use trailing underscore.
40 if (window.domAutomationController) {
42 var ___interactiveUiTestsMode = true;
45 * Test suite for interactive UI tests.
48 TestSuite = function()
50 this.controlTaken_ = false;
56 * Reports test failure.
57 * @param {string} message Failure description.
59 TestSuite.prototype.fail = function(message)
61 if (this.controlTaken_)
62 this.reportFailure_(message);
69 * Equals assertion tests that expected === actual.
70 * @param {Object} expected Expected object.
71 * @param {Object} actual Actual object.
72 * @param {string} opt_message User message to print if the test fails.
74 TestSuite.prototype.assertEquals = function(expected, actual, opt_message)
76 if (expected !== actual) {
77 var message = "Expected: '" + expected + "', but was '" + actual + "'";
79 message = opt_message + "(" + message + ")";
86 * True assertion tests that value == true.
87 * @param {Object} value Actual object.
88 * @param {string} opt_message User message to print if the test fails.
90 TestSuite.prototype.assertTrue = function(value, opt_message)
92 this.assertEquals(true, !!value, opt_message);
97 * Contains assertion tests that string contains substring.
98 * @param {string} string Outer.
99 * @param {string} substring Inner.
101 TestSuite.prototype.assertContains = function(string, substring)
103 if (string.indexOf(substring) === -1)
104 this.fail("Expected to: '" + string + "' to contain '" + substring + "'");
109 * Takes control over execution.
111 TestSuite.prototype.takeControl = function()
113 this.controlTaken_ = true;
114 // Set up guard timer.
116 this.timerId_ = setTimeout(function() {
117 self.reportFailure_("Timeout exceeded: 20 sec");
123 * Releases control over execution.
125 TestSuite.prototype.releaseControl = function()
127 if (this.timerId_ !== -1) {
128 clearTimeout(this.timerId_);
136 * Async tests use this one to report that they are completed.
138 TestSuite.prototype.reportOk_ = function()
140 window.domAutomationController.send("[OK]");
145 * Async tests use this one to report failures.
147 TestSuite.prototype.reportFailure_ = function(error)
149 if (this.timerId_ !== -1) {
150 clearTimeout(this.timerId_);
153 window.domAutomationController.send("[FAILED] " + error);
158 * Runs all global functions starting with "test" as unit tests.
160 TestSuite.prototype.runTest = function(testName)
164 if (!this.controlTaken_)
167 this.reportFailure_(e);
173 * @param {string} panelName Name of the panel to show.
175 TestSuite.prototype.showPanel = function(panelName)
177 // Open Scripts panel.
178 var toolbar = document.getElementById("toolbar");
179 var button = toolbar.getElementsByClassName(panelName)[0];
181 this.assertEquals(WebInspector.panels[panelName], WebInspector.currentPanel);
186 * Overrides the method with specified name until it's called first time.
187 * @param {Object} receiver An object whose method to override.
188 * @param {string} methodName Name of the method to override.
189 * @param {Function} override A function that should be called right after the
190 * overriden method returns.
191 * @param {boolean} opt_sticky Whether restore original method after first run
194 TestSuite.prototype.addSniffer = function(receiver, methodName, override, opt_sticky)
196 var orig = receiver[methodName];
197 if (typeof orig !== "function")
198 this.fail("Cannot find method to override: " + methodName);
200 receiver[methodName] = function(var_args) {
202 var result = orig.apply(this, arguments);
205 receiver[methodName] = orig;
207 // In case of exception the override won't be called.
209 override.apply(this, arguments);
211 test.fail("Exception in overriden method '" + methodName + "': " + e);
222 * Tests that resources tab is enabled when corresponding item is selected.
224 TestSuite.prototype.testEnableResourcesTab = function()
226 this.showPanel("resources");
229 this.addSniffer(WebInspector, "updateResource",
231 test.assertEquals("simple_page.html", payload.lastPathComponent);
232 WebInspector.panels.resources.refresh();
233 WebInspector.panels.resources.revealAndSelectItem(WebInspector.resources[payload.id]);
235 test.releaseControl();
238 // Following call should lead to reload that we capture in the
239 // addResource override.
240 WebInspector.panels.resources._enableResourceTracking();
242 // We now have some time to report results to controller.
248 * Tests that profiler works.
250 TestSuite.prototype.testProfilerTab = function()
252 this.showPanel("profiles");
254 var panel = WebInspector.panels.profiles;
257 function findDisplayedNode() {
258 var node = panel.visibleView.profileDataGridTree.children[0];
260 // Profile hadn't been queried yet, re-schedule.
261 window.setTimeout(findDisplayedNode, 100);
265 // Iterate over displayed functions and search for a function
266 // that is called "fib" or "eternal_fib". If found, this will mean
267 // that we actually have profiled page's code.
269 if (node.functionName.indexOf("fib") !== -1)
270 test.releaseControl();
271 node = node.traverseNextNode(true, null, true);
277 function findVisibleView() {
278 if (!panel.visibleView) {
279 setTimeout(findVisibleView, 0);
282 setTimeout(findDisplayedNode, 0);
291 * Tests that heap profiler works.
293 TestSuite.prototype.testHeapProfiler = function()
295 this.showPanel("profiles");
297 var panel = WebInspector.panels.profiles;
300 function findDisplayedNode() {
301 var node = panel.visibleView.dataGrid.children[0];
303 // Profile hadn't been queried yet, re-schedule.
304 window.setTimeout(findDisplayedNode, 100);
308 // Iterate over displayed functions and find node called "A"
309 // If found, this will mean that we actually have taken heap snapshot.
311 if (node.constructorName.indexOf("A") !== -1) {
312 test.releaseControl();
315 node = node.traverseNextNode(false, null, true);
321 function findVisibleView() {
322 if (!panel.visibleView) {
323 setTimeout(findVisibleView, 0);
326 setTimeout(findDisplayedNode, 0);
329 WebInspector.HeapSnapshotProfileType.prototype.buttonClicked();
336 * Tests that scripts tab can be open and populated with inspected scripts.
338 TestSuite.prototype.testShowScriptsTab = function()
340 this.showPanel("scripts");
342 // There should be at least main page script.
343 this._waitUntilScriptsAreParsed(["debugger_test_page.html"],
345 test.releaseControl();
347 // Wait until all scripts are added to the debugger.
353 * Tests that scripts tab is populated with inspected scripts even if it
354 * hadn't been shown by the moment inspected paged refreshed.
355 * @see http://crbug.com/26312
357 TestSuite.prototype.testScriptsTabIsPopulatedOnInspectedPageRefresh = function()
360 this.assertEquals(WebInspector.panels.elements, WebInspector.currentPanel, "Elements panel should be current one.");
362 this.addSniffer(WebInspector.panels.scripts, "reset", waitUntilScriptIsParsed);
364 // Reload inspected page. It will reset the debugger agent.
365 test.evaluateInConsole_(
366 "window.location.reload(true);",
367 function(resultText) {});
369 function waitUntilScriptIsParsed() {
370 test.showPanel("scripts");
371 test._waitUntilScriptsAreParsed(["debugger_test_page.html"],
373 test.releaseControl();
377 // Wait until all scripts are added to the debugger.
383 * Tests that scripts list contains content scripts.
385 TestSuite.prototype.testContentScriptIsPresent = function()
387 this.showPanel("scripts");
390 test._waitUntilScriptsAreParsed(
391 ["page_with_content_script.html", "simple_content_script.js"],
393 test.releaseControl();
396 // Wait until all scripts are added to the debugger.
402 * Tests that scripts are not duplicaed on Scripts tab switch.
404 TestSuite.prototype.testNoScriptDuplicatesOnPanelSwitch = function()
408 // There should be two scripts: one for the main page and another
409 // one which is source of console API(see
410 // InjectedScript._ensureCommandLineAPIInstalled).
411 var expectedScriptsCount = 2;
412 var parsedScripts = [];
414 this.showPanel("scripts");
417 function switchToElementsTab() {
418 test.showPanel("elements");
419 setTimeout(switchToScriptsTab, 0);
422 function switchToScriptsTab() {
423 test.showPanel("scripts");
424 setTimeout(checkScriptsPanel, 0);
427 function checkScriptsPanel() {
428 test.assertTrue(!!WebInspector.panels.scripts.visibleView, "No visible script view.");
429 test.assertTrue(test._scriptsAreParsed(["debugger_test_page.html"]), "Some scripts are missing.");
431 test.releaseControl();
434 function checkNoDuplicates() {
435 var scriptSelect = document.getElementById("scripts-files");
436 var options = scriptSelect.options;
437 for (var i = 0; i < options.length; i++) {
438 var scriptName = options[i].text;
439 for (var j = i + 1; j < options.length; j++)
440 test.assertTrue(scriptName !== options[j].text, "Found script duplicates: " + test.optionsToString_(options));
444 test._waitUntilScriptsAreParsed(
445 ["debugger_test_page.html"],
448 setTimeout(switchToElementsTab, 0);
452 // Wait until all scripts are added to the debugger.
457 // Tests that debugger works correctly if pause event occurs when DevTools
458 // frontend is being loaded.
459 TestSuite.prototype.testPauseWhenLoadingDevTools = function()
461 this.showPanel("scripts");
465 functionsOnStack: ["callDebugger"],
467 lineText: " debugger;"
471 // Script execution can already be paused.
472 if (WebInspector.currentPanel.paused) {
473 var callFrame = WebInspector.currentPanel.sidebarPanes.callstack.selectedCallFrame;
474 this.assertEquals(expectations.functionsOnStack[0], callFrame.functionName);
475 var callbackInvoked = false;
476 this._checkSourceFrameWhenLoaded(expectations, function() {
477 callbackInvoked = true;
478 if (test.controlTaken_)
479 test.releaseControl();
481 if (!callbackInvoked) {
487 this._waitForScriptPause(
489 functionsOnStack: ["callDebugger"],
491 lineText: " debugger;"
494 test.releaseControl();
500 // Tests that pressing "Pause" will pause script execution if the script
501 // is already running.
502 TestSuite.prototype.testPauseWhenScriptIsRunning = function()
504 this.showPanel("scripts");
507 test.evaluateInConsole_(
508 'setTimeout("handleClick()" , 0)',
509 function(resultText) {
510 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText);
511 testScriptPauseAfterDelay();
514 // Wait for some time to make sure that inspected page is running the
516 function testScriptPauseAfterDelay() {
517 setTimeout(testScriptPause, 300);
520 function testScriptPause() {
521 // The script should be in infinite loop. Click "Pause" button to
522 // pause it and wait for the result.
523 WebInspector.panels.scripts.pauseButton.click();
525 test._waitForScriptPause(
527 functionsOnStack: ["handleClick", ""],
529 lineText: " while(true) {"
532 test.releaseControl();
541 * Serializes options collection to string.
542 * @param {HTMLOptionsCollection} options
545 TestSuite.prototype.optionsToString_ = function(options)
548 for (var i = 0; i < options.length; i++)
549 names.push('"' + options[i].text + '"');
550 return names.join(",");
555 * Ensures that main HTML resource is selected in Scripts panel and that its
556 * source frame is setup. Invokes the callback when the condition is satisfied.
557 * @param {HTMLOptionsCollection} options
558 * @param {function(WebInspector.SourceView,string)} callback
560 TestSuite.prototype.showMainPageScriptSource_ = function(scriptName, callback)
564 var scriptSelect = document.getElementById("scripts-files");
565 var options = scriptSelect.options;
567 test.assertTrue(options.length, "Scripts list is empty");
569 // Select page's script if it's not current option.
571 if (options[scriptSelect.selectedIndex].text === scriptName)
572 scriptResource = options[scriptSelect.selectedIndex].representedObject;
574 var pageScriptIndex = -1;
575 for (var i = 0; i < options.length; i++) {
576 if (options[i].text === scriptName) {
581 test.assertTrue(-1 !== pageScriptIndex, "Script with url " + scriptName + " not found among " + test.optionsToString_(options));
582 scriptResource = options[pageScriptIndex].representedObject;
584 // Current panel is "Scripts".
585 WebInspector.currentPanel._showScriptOrResource(scriptResource);
586 test.assertEquals(pageScriptIndex, scriptSelect.selectedIndex, "Unexpected selected option index.");
589 test.assertTrue(scriptResource instanceof WebInspector.Resource,
590 "Unexpected resource class.");
591 test.assertTrue(!!scriptResource.url, "Resource URL is null.");
592 test.assertTrue(scriptResource.url.search(scriptName + "$") !== -1, "Main HTML resource should be selected.");
594 var scriptsPanel = WebInspector.panels.scripts;
596 var view = scriptsPanel.visibleView;
597 test.assertTrue(view instanceof WebInspector.SourceView);
599 if (!view.sourceFrame._loaded) {
600 test.addSniffer(view, "_sourceFrameSetupFinished", function(event) {
601 callback(view, scriptResource.url);
604 callback(view, scriptResource.url);
609 * Evaluates the code in the console as if user typed it manually and invokes
610 * the callback when the result message is received and added to the console.
611 * @param {string} code
612 * @param {function(string)} callback
614 TestSuite.prototype.evaluateInConsole_ = function(code, callback)
616 WebInspector.showConsole();
617 WebInspector.console.prompt.text = code;
618 WebInspector.console.promptElement.dispatchEvent( TestSuite.createKeyEvent("Enter"));
620 this.addSniffer(WebInspector.ConsoleView.prototype, "addMessage",
621 function(commandResult) {
622 callback(commandResult.toMessageElement().textContent);
628 * Tests that console auto completion works when script execution is paused.
630 TestSuite.prototype.testCompletionOnPause = function()
632 this.showPanel("scripts");
634 this._executeCodeWhenScriptsAreParsed("handleClick()", ["completion_on_pause.html"]);
636 this._waitForScriptPause(
638 functionsOnStack: ["innerFunction", "handleClick", ""],
640 lineText: " debugger;"
644 function showConsole() {
645 if (WebInspector.currentFocusElement === WebInspector.console.promptElement)
646 testLocalsCompletion();
648 test.addSniffer(WebInspector.console, "afterShow", testLocalsCompletion);
649 WebInspector.showConsole();
653 function testLocalsCompletion() {
654 checkCompletions("th", ["parameter1", "closureLocal", "p", "createClosureLocal"], testThisCompletion);
657 function testThisCompletion() {
658 checkCompletions("this.", ["field1", "field2", "m"], testFieldCompletion);
661 function testFieldCompletion() {
662 checkCompletions("this.field1.", ["id", "name"], function() { test.releaseControl(); });
665 function checkCompletions(expression, expectedProperties, callback) {
666 test.addSniffer(WebInspector.console, "_reportCompletions",
667 function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, result, isException) {
668 test.assertTrue(!isException, "Exception while collecting completions");
669 for (var i = 0; i < expectedProperties.length; i++) {
670 var name = expectedProperties[i];
671 test.assertTrue(result[name], "Name " + name + " not found among the completions: " + JSON.stringify(result));
673 setTimeout(callback, 0);
675 WebInspector.console.prompt.text = expression;
676 WebInspector.console.prompt.autoCompleteSoon();
684 * Checks current execution line against expectations.
685 * @param {WebInspector.SourceFrame} sourceFrame
686 * @param {number} lineNumber Expected line number
687 * @param {string} lineContent Expected line text
689 TestSuite.prototype._checkExecutionLine = function(sourceFrame, lineNumber, lineContent)
691 this.assertEquals(lineNumber, sourceFrame.executionLine, "Unexpected execution line number.");
692 this.assertEquals(lineContent, sourceFrame._textModel.line(lineNumber - 1), "Unexpected execution line text.");
697 * Checks that all expected scripts are present in the scripts list
698 * in the Scripts panel.
699 * @param {Array.<string>} expected Regular expressions describing
700 * expected script names.
701 * @return {boolean} Whether all the scripts are in "scripts-files" select
704 TestSuite.prototype._scriptsAreParsed = function(expected)
706 var scriptSelect = document.getElementById("scripts-files");
707 var options = scriptSelect.options;
709 // Check that at least all the expected scripts are present.
710 var missing = expected.slice(0);
711 for (var i = 0 ; i < options.length; i++) {
712 for (var j = 0; j < missing.length; j++) {
713 if (options[i].text.search(missing[j]) !== -1) {
714 missing.splice(j, 1);
719 return missing.length === 0;
724 * Waits for script pause, checks expectations, and invokes the callback.
725 * @param {Object} expectations Dictionary of expectations
726 * @param {function():void} callback
728 TestSuite.prototype._waitForScriptPause = function(expectations, callback)
731 // Wait until script is paused.
733 WebInspector.debuggerModel,
736 var callFrames = details.callFrames;
737 var functionsOnStack = [];
738 for (var i = 0; i < callFrames.length; i++)
739 functionsOnStack.push(callFrames[i].functionName);
741 test.assertEquals(expectations.functionsOnStack.join(","), functionsOnStack.join(","), "Unexpected stack.");
743 // Check that execution line where the script is paused is
745 test._checkSourceFrameWhenLoaded(expectations, callback);
751 * Waits for current source frame to load, checks expectations, and invokes
753 * @param {Object} expectations Dictionary of expectations
754 * @param {function():void} callback
756 TestSuite.prototype._checkSourceFrameWhenLoaded = function(expectations, callback)
760 var frame = WebInspector.currentPanel.visibleView.sourceFrame;
764 setTimeout(function() {
765 test._checkSourceFrameWhenLoaded(expectations, callback);
768 function checkExecLine() {
769 test._checkExecutionLine(frame, expectations.lineNumber, expectations.lineText);
776 * Waits until all the scripts are parsed and asynchronously executes the code
777 * in the inspected page.
779 TestSuite.prototype._executeCodeWhenScriptsAreParsed = function(code, expectedScripts)
783 function executeFunctionInInspectedPage() {
784 // Since breakpoints are ignored in evals' calculate() function is
785 // execute after zero-timeout so that the breakpoint is hit.
786 test.evaluateInConsole_(
787 'setTimeout("' + code + '" , 0)',
788 function(resultText) {
789 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText + ". Code: " + code);
793 test._waitUntilScriptsAreParsed(expectedScripts, executeFunctionInInspectedPage);
798 * Waits until all the scripts are parsed and invokes the callback.
800 TestSuite.prototype._waitUntilScriptsAreParsed = function(expectedScripts, callback)
804 function waitForAllScripts() {
805 if (test._scriptsAreParsed(expectedScripts))
808 test.addSniffer(WebInspector.debuggerModel, "parsedScriptSource", waitForAllScripts);
816 * Executes the 'code' with InjectedScriptAccess.getProperties overriden
817 * so that all callbacks passed to InjectedScriptAccess.getProperties are
818 * extended with the "hook".
819 * @param {Function} hook The hook function.
820 * @param {Function} code A code snippet to be executed.
822 TestSuite.prototype._hookGetPropertiesCallback = function(hook, code)
824 var accessor = InjectedScriptAccess.prototype;
825 var orig = accessor.getProperties;
826 accessor.getProperties = function(objectProxy, ignoreHasOwnProperty, abbreviate, callback) {
827 orig.call(this, objectProxy, ignoreHasOwnProperty, abbreviate,
829 callback.apply(this, arguments);
836 accessor.getProperties = orig;
842 * Tests "Pause" button will pause debugger when a snippet is evaluated.
844 TestSuite.prototype.testPauseInEval = function()
846 this.showPanel("scripts");
850 var pauseButton = document.getElementById("scripts-pause");
853 devtools.tools.evaluateJavaScript("fib(10)");
855 this.addSniffer(WebInspector.debuggerModel, "pausedScript",
857 test.releaseControl();
865 * Key event with given key identifier.
867 TestSuite.createKeyEvent = function(keyIdentifier)
869 var evt = document.createEvent("KeyboardEvent");
870 evt.initKeyboardEvent("keydown", true /* can bubble */, true /* can cancel */, null /* view */, keyIdentifier, "");
876 * Test runner for the test suite.
882 * Run each test from the test suit on a fresh instance of the suite.
884 uiTests.runAllTests = function()
886 // For debugging purposes.
887 for (var name in TestSuite.prototype) {
888 if (name.substring(0, 4) === "test" && typeof TestSuite.prototype[name] === "function")
889 uiTests.runTest(name);
895 * Run specified test on a fresh instance of the test suite.
896 * @param {string} name Name of a test method from TestSuite class.
898 uiTests.runTest = function(name)
900 if (uiTests._populatedInterface)
901 new TestSuite().runTest(name);
903 uiTests._pendingTestName = name;
910 uiTests._populatedInterface = true;
911 var name = uiTests._pendingTestName;
912 delete uiTests._pendingTestName;
914 new TestSuite().runTest(name);
917 var oldShowElementsPanel = WebInspector.showElementsPanel;
918 WebInspector.showElementsPanel = function()
920 oldShowElementsPanel.call(this);
924 var oldShowPanel = WebInspector.showPanel;
925 WebInspector.showPanel = function(name)
927 oldShowPanel.call(this, name);