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 + ")";
85 * True assertion tests that value == true.
86 * @param {Object} value Actual object.
87 * @param {string} opt_message User message to print if the test fails.
89 TestSuite.prototype.assertTrue = function(value, opt_message)
91 this.assertEquals(true, !!value, opt_message);
96 * Contains assertion tests that string contains substring.
97 * @param {string} string Outer.
98 * @param {string} substring Inner.
100 TestSuite.prototype.assertContains = function(string, substring)
102 if (string.indexOf(substring) === -1)
103 this.fail("Expected to: '" + string + "' to contain '" + substring + "'");
108 * Takes control over execution.
110 TestSuite.prototype.takeControl = function()
112 this.controlTaken_ = true;
113 // Set up guard timer.
115 this.timerId_ = setTimeout(function() {
116 self.reportFailure_("Timeout exceeded: 20 sec");
122 * Releases control over execution.
124 TestSuite.prototype.releaseControl = function()
126 if (this.timerId_ !== -1) {
127 clearTimeout(this.timerId_);
135 * Async tests use this one to report that they are completed.
137 TestSuite.prototype.reportOk_ = function()
139 window.domAutomationController.send("[OK]");
144 * Async tests use this one to report failures.
146 TestSuite.prototype.reportFailure_ = function(error)
148 if (this.timerId_ !== -1) {
149 clearTimeout(this.timerId_);
152 window.domAutomationController.send("[FAILED] " + error);
157 * Runs all global functions starting with "test" as unit tests.
159 TestSuite.prototype.runTest = function(testName)
163 if (!this.controlTaken_)
166 this.reportFailure_(e);
172 * @param {string} panelName Name of the panel to show.
174 TestSuite.prototype.showPanel = function(panelName)
176 // Open Scripts panel.
177 var toolbar = document.getElementById("toolbar");
178 var button = toolbar.getElementsByClassName(panelName)[0];
180 this.assertEquals(WebInspector.panels[panelName], WebInspector.currentPanel);
185 * Overrides the method with specified name until it's called first time.
186 * @param {Object} receiver An object whose method to override.
187 * @param {string} methodName Name of the method to override.
188 * @param {Function} override A function that should be called right after the
189 * overriden method returns.
190 * @param {boolean} opt_sticky Whether restore original method after first run
193 TestSuite.prototype.addSniffer = function(receiver, methodName, override, opt_sticky)
195 var orig = receiver[methodName];
196 if (typeof orig !== "function")
197 this.fail("Cannot find method to override: " + methodName);
199 receiver[methodName] = function(var_args) {
201 var result = orig.apply(this, arguments);
204 receiver[methodName] = orig;
206 // In case of exception the override won't be called.
208 override.apply(this, arguments);
210 test.fail("Exception in overriden method '" + methodName + "': " + e);
217 TestSuite.prototype.testEnableResourcesTab = function()
219 // FIXME once reference is removed downstream.
222 TestSuite.prototype.testCompletionOnPause = function()
224 // FIXME once reference is removed downstream.
231 * Tests that scripts tab can be open and populated with inspected scripts.
233 TestSuite.prototype.testShowScriptsTab = function()
235 this.showPanel("scripts");
237 // There should be at least main page script.
238 this._waitUntilScriptsAreParsed(["debugger_test_page.html"],
240 test.releaseControl();
242 // Wait until all scripts are added to the debugger.
248 * Tests that scripts tab is populated with inspected scripts even if it
249 * hadn't been shown by the moment inspected paged refreshed.
250 * @see http://crbug.com/26312
252 TestSuite.prototype.testScriptsTabIsPopulatedOnInspectedPageRefresh = function()
255 this.assertEquals(WebInspector.panels.elements, WebInspector.currentPanel, "Elements panel should be current one.");
257 this.addSniffer(WebInspector.panels.scripts, "reset", waitUntilScriptIsParsed);
259 // Reload inspected page. It will reset the debugger agent.
260 test.evaluateInConsole_(
261 "window.location.reload(true);",
262 function(resultText) {});
264 function waitUntilScriptIsParsed() {
265 test.showPanel("scripts");
266 test._waitUntilScriptsAreParsed(["debugger_test_page.html"],
268 test.releaseControl();
272 // Wait until all scripts are added to the debugger.
278 * Tests that scripts list contains content scripts.
280 TestSuite.prototype.testContentScriptIsPresent = function()
282 this.showPanel("scripts");
285 test._waitUntilScriptsAreParsed(
286 ["page_with_content_script.html", "simple_content_script.js"],
288 test.releaseControl();
291 // Wait until all scripts are added to the debugger.
297 * Tests that scripts are not duplicaed on Scripts tab switch.
299 TestSuite.prototype.testNoScriptDuplicatesOnPanelSwitch = function()
303 // There should be two scripts: one for the main page and another
304 // one which is source of console API(see
305 // InjectedScript._ensureCommandLineAPIInstalled).
306 var expectedScriptsCount = 2;
307 var parsedScripts = [];
309 this.showPanel("scripts");
312 function switchToElementsTab() {
313 test.showPanel("elements");
314 setTimeout(switchToScriptsTab, 0);
317 function switchToScriptsTab() {
318 test.showPanel("scripts");
319 setTimeout(checkScriptsPanel, 0);
322 function checkScriptsPanel() {
323 test.assertTrue(!!WebInspector.panels.scripts.visibleView, "No visible script view.");
324 test.assertTrue(test._scriptsAreParsed(["debugger_test_page.html"]), "Some scripts are missing.");
326 test.releaseControl();
329 function checkNoDuplicates() {
330 var scriptSelect = document.getElementById("scripts-files");
331 var options = scriptSelect.options;
332 for (var i = 0; i < options.length; i++) {
333 var scriptName = options[i].text;
334 for (var j = i + 1; j < options.length; j++)
335 test.assertTrue(scriptName !== options[j].text, "Found script duplicates: " + test.optionsToString_(options));
339 test._waitUntilScriptsAreParsed(
340 ["debugger_test_page.html"],
343 setTimeout(switchToElementsTab, 0);
347 // Wait until all scripts are added to the debugger.
352 // Tests that debugger works correctly if pause event occurs when DevTools
353 // frontend is being loaded.
354 TestSuite.prototype.testPauseWhenLoadingDevTools = function()
356 this.showPanel("scripts");
360 functionsOnStack: ["callDebugger"],
362 lineText: " debugger;"
366 // Script execution can already be paused.
367 if (WebInspector.currentPanel.paused) {
368 var callFrame = WebInspector.currentPanel._presentationModel.selectedCallFrame;
369 this.assertEquals(expectations.functionsOnStack[0], callFrame.functionName);
370 var callbackInvoked = false;
371 this._checkSourceFrameWhenLoaded(expectations, function() {
372 callbackInvoked = true;
373 if (test.controlTaken_)
374 test.releaseControl();
376 if (!callbackInvoked) {
382 this._waitForScriptPause(
384 functionsOnStack: ["callDebugger"],
386 lineText: " debugger;"
389 test.releaseControl();
395 // Tests that pressing "Pause" will pause script execution if the script
396 // is already running.
397 TestSuite.prototype.testPauseWhenScriptIsRunning = function()
399 this.showPanel("scripts");
402 test.evaluateInConsole_(
403 'setTimeout("handleClick()" , 0)',
404 function(resultText) {
405 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText);
406 testScriptPauseAfterDelay();
409 // Wait for some time to make sure that inspected page is running the
411 function testScriptPauseAfterDelay() {
412 setTimeout(testScriptPause, 300);
415 function testScriptPause() {
416 // The script should be in infinite loop. Click "Pause" button to
417 // pause it and wait for the result.
418 WebInspector.panels.scripts.pauseButton.click();
420 test._waitForScriptPause(
422 functionsOnStack: ["handleClick", ""],
424 lineText: " while(true) {"
427 test.releaseControl();
436 * Tests network size.
438 TestSuite.prototype.testNetworkSize = function()
442 function finishResource(resource, finishTime)
444 test.assertEquals(219, resource.transferSize, "Incorrect total encoded data length");
445 test.assertEquals(25, resource.resourceSize, "Incorrect total data length");
446 test.releaseControl();
449 this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishResource", finishResource);
451 // Reload inspected page to sniff network events
452 test.evaluateInConsole_("window.location.reload(true);", function(resultText) {});
459 * Tests network sync size.
461 TestSuite.prototype.testNetworkSyncSize = function()
465 function finishResource(resource, finishTime)
467 test.assertEquals(219, resource.transferSize, "Incorrect total encoded data length");
468 test.assertEquals(25, resource.resourceSize, "Incorrect total data length");
469 test.releaseControl();
472 this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishResource", finishResource);
474 // Send synchronous XHR to sniff network events
475 test.evaluateInConsole_("var xhr = new XMLHttpRequest(); xhr.open(\"GET\", \"chunked\", false); xhr.send(null);", function() {});
482 * Tests network raw headers text.
484 TestSuite.prototype.testNetworkRawHeadersText = function()
488 function finishResource(resource, finishTime)
490 if (!resource.responseHeadersText)
491 test.fail("Failure: resource does not have response headers text");
492 test.assertEquals(164, resource.responseHeadersText.length, "Incorrect response headers text length");
493 test.releaseControl();
496 this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishResource", finishResource);
498 // Reload inspected page to sniff network events
499 test.evaluateInConsole_("window.location.reload(true);", function(resultText) {});
506 * Tests network timing.
508 TestSuite.prototype.testNetworkTiming = function()
512 function finishResource(resource, finishTime)
514 // Setting relaxed expectations to reduce flakiness.
515 // Server sends headers after 100ms, then sends data during another 100ms.
516 // We expect these times to be measured at least as 70ms.
517 test.assertTrue(resource.timing.receiveHeadersEnd - resource.timing.connectStart >= 70,
518 "Time between receiveHeadersEnd and connectStart should be >=70ms, but was " +
519 "receiveHeadersEnd=" + resource.timing.receiveHeadersEnd + ", connectStart=" + resource.timing.connectStart + ".");
520 test.assertTrue(resource.responseReceivedTime - resource.startTime >= 0.07,
521 "Time between responseReceivedTime and startTime should be >=0.07s, but was " +
522 "responseReceivedTime=" + resource.responseReceivedTime + ", startTime=" + resource.startTime + ".");
523 test.assertTrue(resource.endTime - resource.startTime >= 0.14,
524 "Time between endTime and startTime should be >=0.14s, but was " +
525 "endtime=" + resource.endTime + ", startTime=" + resource.startTime + ".");
527 test.releaseControl();
530 this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishResource", finishResource);
532 // Reload inspected page to sniff network events
533 test.evaluateInConsole_("window.location.reload(true);", function(resultText) {});
539 TestSuite.prototype.testSharedWorker = function()
541 function didEvaluateInConsole(resultText) {
542 this.assertEquals("2011", resultText);
543 this.releaseControl();
545 this.evaluateInConsole_("globalVar", didEvaluateInConsole.bind(this));
551 * Serializes options collection to string.
552 * @param {HTMLOptionsCollection} options
555 TestSuite.prototype.optionsToString_ = function(options)
558 for (var i = 0; i < options.length; i++)
559 names.push('"' + options[i].text + '"');
560 return names.join(",");
565 * Ensures that main HTML resource is selected in Scripts panel and that its
566 * source frame is setup. Invokes the callback when the condition is satisfied.
567 * @param {HTMLOptionsCollection} options
568 * @param {function(WebInspector.SourceView,string)} callback
570 TestSuite.prototype.showMainPageScriptSource_ = function(scriptName, callback)
574 var scriptSelect = document.getElementById("scripts-files");
575 var options = scriptSelect.options;
577 test.assertTrue(options.length, "Scripts list is empty");
579 // Select page's script if it's not current option.
581 if (options[scriptSelect.selectedIndex].text === scriptName)
582 scriptResource = options[scriptSelect.selectedIndex].representedObject;
584 var pageScriptIndex = -1;
585 for (var i = 0; i < options.length; i++) {
586 if (options[i].text === scriptName) {
591 test.assertTrue(-1 !== pageScriptIndex, "Script with url " + scriptName + " not found among " + test.optionsToString_(options));
592 scriptResource = options[pageScriptIndex].representedObject;
594 // Current panel is "Scripts".
595 WebInspector.currentPanel._showScriptOrResource(scriptResource);
596 test.assertEquals(pageScriptIndex, scriptSelect.selectedIndex, "Unexpected selected option index.");
599 test.assertTrue(scriptResource instanceof WebInspector.Resource,
600 "Unexpected resource class.");
601 test.assertTrue(!!scriptResource.url, "Resource URL is null.");
602 test.assertTrue(scriptResource.url.search(scriptName + "$") !== -1, "Main HTML resource should be selected.");
604 var scriptsPanel = WebInspector.panels.scripts;
606 var view = scriptsPanel.visibleView;
607 test.assertTrue(view instanceof WebInspector.SourceView);
609 if (!view.sourceFrame._loaded) {
610 test.addSniffer(view, "_sourceFrameSetupFinished", function(event) {
611 callback(view, scriptResource.url);
614 callback(view, scriptResource.url);
619 * Evaluates the code in the console as if user typed it manually and invokes
620 * the callback when the result message is received and added to the console.
621 * @param {string} code
622 * @param {function(string)} callback
624 TestSuite.prototype.evaluateInConsole_ = function(code, callback)
626 WebInspector.showConsole();
627 WebInspector.consoleView.prompt.text = code;
628 WebInspector.consoleView.promptElement.dispatchEvent(TestSuite.createKeyEvent("Enter"));
630 this.addSniffer(WebInspector.ConsoleView.prototype, "_appendConsoleMessage",
631 function(commandResult) {
632 callback(commandResult.toMessageElement().textContent);
638 * Checks current execution line against expectations.
639 * @param {WebInspector.SourceFrame} sourceFrame
640 * @param {number} lineNumber Expected line number
641 * @param {string} lineContent Expected line text
643 TestSuite.prototype._checkExecutionLine = function(sourceFrame, lineNumber, lineContent)
645 this.assertEquals(lineNumber, sourceFrame._executionLineNumber + 1, "Unexpected execution line number.");
646 this.assertEquals(lineContent, sourceFrame._textModel.line(lineNumber - 1), "Unexpected execution line text.");
651 * Checks that all expected scripts are present in the scripts list
652 * in the Scripts panel.
653 * @param {Array.<string>} expected Regular expressions describing
654 * expected script names.
655 * @return {boolean} Whether all the scripts are in "scripts-files" select
658 TestSuite.prototype._scriptsAreParsed = function(expected)
660 var scriptSelect = document.getElementById("scripts-files");
661 var options = scriptSelect.options;
663 // Check that at least all the expected scripts are present.
664 var missing = expected.slice(0);
665 for (var i = 0 ; i < options.length; i++) {
666 for (var j = 0; j < missing.length; j++) {
667 if (options[i].text.search(missing[j]) !== -1) {
668 missing.splice(j, 1);
673 return missing.length === 0;
678 * Waits for script pause, checks expectations, and invokes the callback.
679 * @param {Object} expectations Dictionary of expectations
680 * @param {function():void} callback
682 TestSuite.prototype._waitForScriptPause = function(expectations, callback)
685 // Wait until script is paused.
687 WebInspector.debuggerModel,
690 var callFrames = details.callFrames;
691 var functionsOnStack = [];
692 for (var i = 0; i < callFrames.length; i++)
693 functionsOnStack.push(callFrames[i].functionName);
695 test.assertEquals(expectations.functionsOnStack.join(","), functionsOnStack.join(","), "Unexpected stack.");
697 // Check that execution line where the script is paused is
699 test._checkSourceFrameWhenLoaded(expectations, callback);
705 * Waits for current source frame to load, checks expectations, and invokes
707 * @param {Object} expectations Dictionary of expectations
708 * @param {function():void} callback
710 TestSuite.prototype._checkSourceFrameWhenLoaded = function(expectations, callback)
714 var frame = WebInspector.currentPanel.visibleView;
716 if (frame._textViewer)
719 setTimeout(function() {
720 test._checkSourceFrameWhenLoaded(expectations, callback);
723 function checkExecLine() {
724 test._checkExecutionLine(frame, expectations.lineNumber, expectations.lineText);
731 * Waits until all the scripts are parsed and asynchronously executes the code
732 * in the inspected page.
734 TestSuite.prototype._executeCodeWhenScriptsAreParsed = function(code, expectedScripts)
738 function executeFunctionInInspectedPage() {
739 // Since breakpoints are ignored in evals' calculate() function is
740 // execute after zero-timeout so that the breakpoint is hit.
741 test.evaluateInConsole_(
742 'setTimeout("' + code + '" , 0)',
743 function(resultText) {
744 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText + ". Code: " + code);
748 test._waitUntilScriptsAreParsed(expectedScripts, executeFunctionInInspectedPage);
753 * Waits until all the scripts are parsed and invokes the callback.
755 TestSuite.prototype._waitUntilScriptsAreParsed = function(expectedScripts, callback)
759 function waitForAllScripts() {
760 if (test._scriptsAreParsed(expectedScripts))
763 test.addSniffer(WebInspector.panels.scripts, "_addOptionToFilesSelect", waitForAllScripts);
771 * Key event with given key identifier.
773 TestSuite.createKeyEvent = function(keyIdentifier)
775 var evt = document.createEvent("KeyboardEvent");
776 evt.initKeyboardEvent("keydown", true /* can bubble */, true /* can cancel */, null /* view */, keyIdentifier, "");
782 * Test runner for the test suite.
788 * Run each test from the test suit on a fresh instance of the suite.
790 uiTests.runAllTests = function()
792 // For debugging purposes.
793 for (var name in TestSuite.prototype) {
794 if (name.substring(0, 4) === "test" && typeof TestSuite.prototype[name] === "function")
795 uiTests.runTest(name);
801 * Run specified test on a fresh instance of the test suite.
802 * @param {string} name Name of a test method from TestSuite class.
804 uiTests.runTest = function(name)
806 if (uiTests._populatedInterface)
807 new TestSuite().runTest(name);
809 uiTests._pendingTestName = name;
816 uiTests._populatedInterface = true;
817 var name = uiTests._pendingTestName;
818 delete uiTests._pendingTestName;
820 new TestSuite().runTest(name);
823 var oldShowElementsPanel = WebInspector.showElementsPanel;
824 WebInspector.showElementsPanel = function()
826 oldShowElementsPanel.call(this);
830 var oldShowPanel = WebInspector.showPanel;
831 WebInspector.showPanel = function(name)
833 oldShowPanel.call(this, name);