1 /* 2 * Copyright (C) 2010 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 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 13 * distribution. 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. 17 * 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. 29 */ 30 31 32 /** 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. 38 */ 39 40 if (window.domAutomationController) { 41 42 var ___interactiveUiTestsMode = true; 43 44 /** 45 * Test suite for interactive UI tests. 46 * @constructor 47 */ 48 TestSuite = function() 49 { 50 this.controlTaken_ = false; 51 this.timerId_ = -1; 52 }; 53 54 55 /** 56 * Reports test failure. 57 * @param {string} message Failure description. 58 */ 59 TestSuite.prototype.fail = function(message) 60 { 61 if (this.controlTaken_) 62 this.reportFailure_(message); 63 else 64 throw message; 65 }; 66 67 68 /** 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. 73 */ 74 TestSuite.prototype.assertEquals = function(expected, actual, opt_message) 75 { 76 if (expected !== actual) { 77 var message = "Expected: '" + expected + "', but was '" + actual + "'"; 78 if (opt_message) 79 message = opt_message + "(" + message + ")"; 80 this.fail(message); 81 } 82 }; 83 84 85 /** 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. 89 */ 90 TestSuite.prototype.assertTrue = function(value, opt_message) 91 { 92 this.assertEquals(true, !!value, opt_message); 93 }; 94 95 96 /** 97 * Contains assertion tests that string contains substring. 98 * @param {string} string Outer. 99 * @param {string} substring Inner. 100 */ 101 TestSuite.prototype.assertContains = function(string, substring) 102 { 103 if (string.indexOf(substring) === -1) 104 this.fail("Expected to: '" + string + "' to contain '" + substring + "'"); 105 }; 106 107 108 /** 109 * Takes control over execution. 110 */ 111 TestSuite.prototype.takeControl = function() 112 { 113 this.controlTaken_ = true; 114 // Set up guard timer. 115 var self = this; 116 this.timerId_ = setTimeout(function() { 117 self.reportFailure_("Timeout exceeded: 20 sec"); 118 }, 20000); 119 }; 120 121 122 /** 123 * Releases control over execution. 124 */ 125 TestSuite.prototype.releaseControl = function() 126 { 127 if (this.timerId_ !== -1) { 128 clearTimeout(this.timerId_); 129 this.timerId_ = -1; 130 } 131 this.reportOk_(); 132 }; 133 134 135 /** 136 * Async tests use this one to report that they are completed. 137 */ 138 TestSuite.prototype.reportOk_ = function() 139 { 140 window.domAutomationController.send("[OK]"); 141 }; 142 143 144 /** 145 * Async tests use this one to report failures. 146 */ 147 TestSuite.prototype.reportFailure_ = function(error) 148 { 149 if (this.timerId_ !== -1) { 150 clearTimeout(this.timerId_); 151 this.timerId_ = -1; 152 } 153 window.domAutomationController.send("[FAILED] " + error); 154 }; 155 156 157 /** 158 * Runs all global functions starting with "test" as unit tests. 159 */ 160 TestSuite.prototype.runTest = function(testName) 161 { 162 try { 163 this[testName](); 164 if (!this.controlTaken_) 165 this.reportOk_(); 166 } catch (e) { 167 this.reportFailure_(e); 168 } 169 }; 170 171 172 /** 173 * @param {string} panelName Name of the panel to show. 174 */ 175 TestSuite.prototype.showPanel = function(panelName) 176 { 177 // Open Scripts panel. 178 var toolbar = document.getElementById("toolbar"); 179 var button = toolbar.getElementsByClassName(panelName)[0]; 180 button.click(); 181 this.assertEquals(WebInspector.panels[panelName], WebInspector.currentPanel); 182 }; 183 184 185 /** 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 192 * or not. 193 */ 194 TestSuite.prototype.addSniffer = function(receiver, methodName, override, opt_sticky) 195 { 196 var orig = receiver[methodName]; 197 if (typeof orig !== "function") 198 this.fail("Cannot find method to override: " + methodName); 199 var test = this; 200 receiver[methodName] = function(var_args) { 201 try { 202 var result = orig.apply(this, arguments); 203 } finally { 204 if (!opt_sticky) 205 receiver[methodName] = orig; 206 } 207 // In case of exception the override won't be called. 208 try { 209 override.apply(this, arguments); 210 } catch (e) { 211 test.fail("Exception in overriden method '" + methodName + "': " + e); 212 } 213 return result; 214 }; 215 }; 216 217 218 // UI Tests 219 220 221 /** 222 * Tests that the real injected host is present in the context. 223 */ 224 TestSuite.prototype.testHostIsPresent = function() 225 { 226 this.assertTrue(typeof InspectorFrontendHost === "object" && !InspectorFrontendHost.isStub); 227 }; 228 229 230 /** 231 * Tests elements tree has an "HTML" root. 232 */ 233 TestSuite.prototype.testElementsTreeRoot = function() 234 { 235 var doc = WebInspector.domAgent.document; 236 this.assertEquals("HTML", doc.documentElement.nodeName); 237 this.assertTrue(doc.documentElement.hasChildNodes()); 238 }; 239 240 241 /** 242 * Tests that main resource is present in the system and that it is 243 * the only resource. 244 */ 245 TestSuite.prototype.testMainResource = function() 246 { 247 var tokens = []; 248 var resources = WebInspector.resources; 249 for (var id in resources) 250 tokens.push(resources[id].lastPathComponent); 251 this.assertEquals("simple_page.html", tokens.join(",")); 252 }; 253 254 255 /** 256 * Tests that resources tab is enabled when corresponding item is selected. 257 */ 258 TestSuite.prototype.testEnableResourcesTab = function() 259 { 260 this.showPanel("resources"); 261 262 var test = this; 263 this.addSniffer(WebInspector, "updateResource", 264 function(identifier, payload) { 265 test.assertEquals("simple_page.html", payload.lastPathComponent); 266 WebInspector.panels.resources.refresh(); 267 WebInspector.panels.resources.revealAndSelectItem(WebInspector.resources[identifier]); 268 269 test.releaseControl(); 270 }); 271 272 // Following call should lead to reload that we capture in the 273 // addResource override. 274 WebInspector.panels.resources._enableResourceTracking(); 275 276 // We now have some time to report results to controller. 277 this.takeControl(); 278 }; 279 280 281 /** 282 * Tests that correct content length is reported for resources. 283 */ 284 TestSuite.prototype.testResourceContentLength = function() 285 { 286 this.showPanel("resources"); 287 var test = this; 288 289 var png = false; 290 var html = false; 291 this.addSniffer(WebInspector, "updateResource", 292 function(identifier, payload) { 293 if (!payload.didLengthChange) 294 return; 295 var resource = WebInspector.resources[identifier]; 296 if (!resource || !resource.url) 297 return; 298 if (resource.url.search("image.html$") !== -1) { 299 var expectedLength = 87; 300 test.assertTrue( 301 resource.contentLength <= expectedLength, 302 "image.html content length is greater thatn expected."); 303 if (expectedLength === resource.contentLength) 304 html = true; 305 } else if (resource.url.search("image.png") !== -1) { 306 var expectedLength = 257796; 307 test.assertTrue( 308 resource.contentLength <= expectedLength, 309 "image.png content length is greater than expected."); 310 if (expectedLength === resource.contentLength) 311 png = true; 312 } 313 if (html && png) { 314 // Wait 1 second before releasing control to check that the content 315 // lengths are not updated anymore. 316 setTimeout(function() { 317 test.releaseControl(); 318 }, 1000); 319 } 320 }, true); 321 322 // Make sure resource tracking is on. 323 WebInspector.panels.resources._enableResourceTracking(); 324 // Reload inspected page to update all resources. 325 test.evaluateInConsole_( 326 "window.location.reload(true);", 327 function(resultText) { 328 test.assertEquals("undefined", resultText, "Unexpected result of reload()."); 329 }); 330 331 // We now have some time to report results to controller. 332 this.takeControl(); 333 }; 334 335 336 /** 337 * Tests resource headers. 338 */ 339 TestSuite.prototype.testResourceHeaders = function() 340 { 341 this.showPanel("resources"); 342 343 var test = this; 344 345 var responseOk = false; 346 var timingOk = false; 347 348 this.addSniffer(WebInspector, "updateResource", 349 function(identifier, payload) { 350 var resource = this.resources[identifier]; 351 if (!resource || resource.mainResource) { 352 // We are only interested in secondary resources in this test. 353 return; 354 } 355 356 var requestHeaders = JSON.stringify(resource.requestHeaders); 357 test.assertContains(requestHeaders, "Accept"); 358 359 if (payload.didResponseChange) { 360 var responseHeaders = JSON.stringify(resource.responseHeaders); 361 test.assertContains(responseHeaders, "Content-type"); 362 test.assertContains(responseHeaders, "Content-Length"); 363 test.assertTrue(typeof resource.responseReceivedTime !== "undefined"); 364 responseOk = true; 365 } 366 367 if (payload.didTimingChange) { 368 test.assertTrue(typeof resource.startTime !== "undefined"); 369 timingOk = true; 370 } 371 372 if (payload.didCompletionChange) { 373 test.assertTrue(responseOk); 374 test.assertTrue(timingOk); 375 test.assertTrue(typeof resource.endTime !== "undefined"); 376 test.releaseControl(); 377 } 378 }, true); 379 380 WebInspector.panels.resources._enableResourceTracking(); 381 this.takeControl(); 382 }; 383 384 385 /** 386 * Tests the mime type of a cached (HTTP 304) resource. 387 */ 388 TestSuite.prototype.testCachedResourceMimeType = function() 389 { 390 this.showPanel("resources"); 391 392 var test = this; 393 var hasReloaded = false; 394 395 this.addSniffer(WebInspector, "updateResource", 396 function(identifier, payload) { 397 var resource = this.resources[identifier]; 398 if (!resource || resource.mainResource) { 399 // We are only interested in secondary resources in this test. 400 return; 401 } 402 403 if (payload.didResponseChange) { 404 // Test server uses a default mime type for JavaScript files. 405 test.assertEquals("text/html", payload.mimeType); 406 if (!hasReloaded) { 407 hasReloaded = true; 408 // Reload inspected page to update all resources. 409 test.evaluateInConsole_("window.location.reload(true);", function() {}); 410 } else 411 test.releaseControl(); 412 } 413 414 }, true); 415 416 WebInspector.panels.resources._enableResourceTracking(); 417 this.takeControl(); 418 }; 419 420 421 /** 422 * Tests that profiler works. 423 */ 424 TestSuite.prototype.testProfilerTab = function() 425 { 426 this.showPanel("profiles"); 427 428 var test = this; 429 this.addSniffer(WebInspector.panels.profiles, "addProfileHeader", 430 function(typeOrProfile, profile) { 431 if (!profile) 432 profile = typeOrProfile; 433 var panel = WebInspector.panels.profiles; 434 panel.showProfile(profile); 435 var node = panel.visibleView.profileDataGridTree.children[0]; 436 // Iterate over displayed functions and search for a function 437 // that is called "fib" or "eternal_fib". If found, it will mean 438 // that we actually have profiled page's code. 439 while (node) { 440 if (node.functionName.indexOf("fib") !== -1) 441 test.releaseControl(); 442 node = node.traverseNextNode(true, null, true); 443 } 444 445 test.fail(); 446 }); 447 var ticksCount = 0; 448 var tickRecord = "\nt,"; 449 this.addSniffer(RemoteProfilerAgent, "didGetLogLines", 450 function(posIgnored, log) { 451 var pos = 0; 452 while ((pos = log.indexOf(tickRecord, pos)) !== -1) { 453 pos += tickRecord.length; 454 ticksCount++; 455 } 456 if (ticksCount > 100) 457 InspectorBackend.stopProfiling(); 458 }, true); 459 460 InspectorBackend.startProfiling(); 461 this.takeControl(); 462 }; 463 464 465 /** 466 * Tests that scripts tab can be open and populated with inspected scripts. 467 */ 468 TestSuite.prototype.testShowScriptsTab = function() 469 { 470 this.showPanel("scripts"); 471 var test = this; 472 // There should be at least main page script. 473 this._waitUntilScriptsAreParsed(["debugger_test_page.html$"], 474 function() { 475 test.releaseControl(); 476 }); 477 // Wait until all scripts are added to the debugger. 478 this.takeControl(); 479 }; 480 481 482 /** 483 * Tests that scripts tab is populated with inspected scripts even if it 484 * hadn't been shown by the moment inspected paged refreshed. 485 * @see http://crbug.com/26312 486 */ 487 TestSuite.prototype.testScriptsTabIsPopulatedOnInspectedPageRefresh = function() 488 { 489 var test = this; 490 this.assertEquals(WebInspector.panels.elements, WebInspector.currentPanel, "Elements panel should be current one."); 491 492 this.addSniffer(devtools.DebuggerAgent.prototype, "reset", waitUntilScriptIsParsed); 493 494 // Reload inspected page. It will reset the debugger agent. 495 test.evaluateInConsole_( 496 "window.location.reload(true);", 497 function(resultText) { 498 test.assertEquals("undefined", resultText, "Unexpected result of reload()."); 499 }); 500 501 function waitUntilScriptIsParsed() { 502 var parsed = devtools.tools.getDebuggerAgent().parsedScripts_; 503 for (var id in parsed) { 504 var url = parsed[id].getUrl(); 505 if (url && url.search("debugger_test_page.html$") !== -1) { 506 checkScriptsPanel(); 507 return; 508 } 509 } 510 test.addSniffer(devtools.DebuggerAgent.prototype, "addScriptInfo_", waitUntilScriptIsParsed); 511 } 512 513 function checkScriptsPanel() { 514 test.showPanel("scripts"); 515 test.assertTrue(test._scriptsAreParsed(["debugger_test_page.html$"]), "Inspected script not found in the scripts list"); 516 test.releaseControl(); 517 } 518 519 // Wait until all scripts are added to the debugger. 520 this.takeControl(); 521 }; 522 523 524 /** 525 * Tests that scripts list contains content scripts. 526 */ 527 TestSuite.prototype.testContentScriptIsPresent = function() 528 { 529 this.showPanel("scripts"); 530 var test = this; 531 532 test._waitUntilScriptsAreParsed( 533 ["page_with_content_script.html$", "simple_content_script.js$"], 534 function() { 535 test.releaseControl(); 536 }); 537 538 // Wait until all scripts are added to the debugger. 539 this.takeControl(); 540 }; 541 542 543 /** 544 * Tests that scripts are not duplicaed on Scripts tab switch. 545 */ 546 TestSuite.prototype.testNoScriptDuplicatesOnPanelSwitch = function() 547 { 548 var test = this; 549 550 // There should be two scripts: one for the main page and another 551 // one which is source of console API(see 552 // InjectedScript._ensureCommandLineAPIInstalled). 553 var expectedScriptsCount = 2; 554 var parsedScripts = []; 555 556 this.showPanel("scripts"); 557 558 559 function switchToElementsTab() { 560 test.showPanel("elements"); 561 setTimeout(switchToScriptsTab, 0); 562 } 563 564 function switchToScriptsTab() { 565 test.showPanel("scripts"); 566 setTimeout(checkScriptsPanel, 0); 567 } 568 569 function checkScriptsPanel() { 570 test.assertTrue(!!WebInspector.panels.scripts.visibleView, "No visible script view."); 571 test.assertTrue(test._scriptsAreParsed(["debugger_test_page.html$"]), "Some scripts are missing."); 572 checkNoDuplicates(); 573 test.releaseControl(); 574 } 575 576 function checkNoDuplicates() { 577 var scriptSelect = document.getElementById("scripts-files"); 578 var options = scriptSelect.options; 579 for (var i = 0; i < options.length; i++) { 580 var scriptName = options[i].text; 581 for (var j = i + 1; j < options.length; j++) 582 test.assertTrue(scriptName !== options[j].text, "Found script duplicates: " + test.optionsToString_(options)); 583 } 584 } 585 586 test._waitUntilScriptsAreParsed( 587 ["debugger_test_page.html$"], 588 function() { 589 checkNoDuplicates(); 590 setTimeout(switchToElementsTab, 0); 591 }); 592 593 594 // Wait until all scripts are added to the debugger. 595 this.takeControl(); 596 }; 597 598 599 /** 600 * Tests that a breakpoint can be set. 601 */ 602 TestSuite.prototype.testSetBreakpoint = function() 603 { 604 var test = this; 605 this.showPanel("scripts"); 606 607 var breakpointLine = 12; 608 609 this._waitUntilScriptsAreParsed(["debugger_test_page.html"], 610 function() { 611 test.showMainPageScriptSource_( 612 "debugger_test_page.html", 613 function(view, url) { 614 view._addBreakpoint(breakpointLine); 615 // Force v8 execution. 616 RemoteDebuggerAgent.processDebugCommands(); 617 test.waitForSetBreakpointResponse_(url, breakpointLine, 618 function() { 619 test.releaseControl(); 620 }); 621 }); 622 }); 623 624 this.takeControl(); 625 }; 626 627 628 /** 629 * Tests that pause on exception works. 630 */ 631 TestSuite.prototype.testPauseOnException = function() 632 { 633 this.showPanel("scripts"); 634 var test = this; 635 636 // TODO(yurys): remove else branch once the states are supported. 637 if (WebInspector.ScriptsPanel.PauseOnExceptionsState) { 638 while (WebInspector.currentPanel.pauseOnExceptionButton.state !== WebInspector.ScriptsPanel.PauseOnExceptionsState.PauseOnUncaughtExceptions) 639 WebInspector.currentPanel.pauseOnExceptionButton.element.click(); 640 } else { 641 // Make sure pause on exceptions is on. 642 if (!WebInspector.currentPanel.pauseOnExceptionButton.toggled) 643 WebInspector.currentPanel.pauseOnExceptionButton.element.click(); 644 } 645 646 this._executeCodeWhenScriptsAreParsed("handleClick()", ["pause_on_exception.html$"]); 647 648 this._waitForScriptPause( 649 { 650 functionsOnStack: ["throwAnException", "handleClick", "(anonymous function)"], 651 lineNumber: 6, 652 lineText: " return unknown_var;" 653 }, 654 function() { 655 test.releaseControl(); 656 }); 657 658 this.takeControl(); 659 }; 660 661 662 // Tests that debugger works correctly if pause event occurs when DevTools 663 // frontend is being loaded. 664 TestSuite.prototype.testPauseWhenLoadingDevTools = function() 665 { 666 this.showPanel("scripts"); 667 var test = this; 668 669 var expectations = { 670 functionsOnStack: ["callDebugger"], 671 lineNumber: 8, 672 lineText: " debugger;" 673 }; 674 675 676 // Script execution can already be paused. 677 if (WebInspector.currentPanel.paused) { 678 var callFrame = WebInspector.currentPanel.sidebarPanes.callstack.selectedCallFrame; 679 this.assertEquals(expectations.functionsOnStack[0], callFrame.functionName); 680 var callbackInvoked = false; 681 this._checkSourceFrameWhenLoaded(expectations, function() { 682 callbackInvoked = true; 683 if (test.controlTaken_) 684 test.releaseControl(); 685 }); 686 if (!callbackInvoked) { 687 test.takeControl(); 688 } 689 return; 690 } 691 692 this._waitForScriptPause( 693 { 694 functionsOnStack: ["callDebugger"], 695 lineNumber: 8, 696 lineText: " debugger;" 697 }, 698 function() { 699 test.releaseControl(); 700 }); 701 this.takeControl(); 702 }; 703 704 705 // Tests that pressing "Pause" will pause script execution if the script 706 // is already running. 707 TestSuite.prototype.testPauseWhenScriptIsRunning = function() 708 { 709 this.showPanel("scripts"); 710 var test = this; 711 712 test.evaluateInConsole_( 713 'setTimeout("handleClick()" , 0)', 714 function(resultText) { 715 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText); 716 testScriptPauseAfterDelay(); 717 }); 718 719 // Wait for some time to make sure that inspected page is running the 720 // infinite loop. 721 function testScriptPauseAfterDelay() { 722 setTimeout(testScriptPause, 300); 723 } 724 725 function testScriptPause() { 726 // The script should be in infinite loop. Click "Pause" button to 727 // pause it and wait for the result. 728 WebInspector.panels.scripts.pauseButton.click(); 729 730 test._waitForScriptPause( 731 { 732 functionsOnStack: ["handleClick", "(anonymous function)"], 733 lineNumber: 5, 734 lineText: " while(true) {" 735 }, 736 function() { 737 test.releaseControl(); 738 }); 739 } 740 741 this.takeControl(); 742 }; 743 744 745 /** 746 * Serializes options collection to string. 747 * @param {HTMLOptionsCollection} options 748 * @return {string} 749 */ 750 TestSuite.prototype.optionsToString_ = function(options) 751 { 752 var names = []; 753 for (var i = 0; i < options.length; i++) 754 names.push('"' + options[i].text + '"'); 755 return names.join(","); 756 }; 757 758 759 /** 760 * Ensures that main HTML resource is selected in Scripts panel and that its 761 * source frame is setup. Invokes the callback when the condition is satisfied. 762 * @param {HTMLOptionsCollection} options 763 * @param {function(WebInspector.SourceView,string)} callback 764 */ 765 TestSuite.prototype.showMainPageScriptSource_ = function(scriptName, callback) 766 { 767 var test = this; 768 769 var scriptSelect = document.getElementById("scripts-files"); 770 var options = scriptSelect.options; 771 772 test.assertTrue(options.length, "Scripts list is empty"); 773 774 // Select page's script if it's not current option. 775 var scriptResource; 776 if (options[scriptSelect.selectedIndex].text === scriptName) 777 scriptResource = options[scriptSelect.selectedIndex].representedObject; 778 else { 779 var pageScriptIndex = -1; 780 for (var i = 0; i < options.length; i++) { 781 if (options[i].text === scriptName) { 782 pageScriptIndex = i; 783 break; 784 } 785 } 786 test.assertTrue(-1 !== pageScriptIndex, "Script with url " + scriptName + " not found among " + test.optionsToString_(options)); 787 scriptResource = options[pageScriptIndex].representedObject; 788 789 // Current panel is "Scripts". 790 WebInspector.currentPanel._showScriptOrResource(scriptResource); 791 test.assertEquals(pageScriptIndex, scriptSelect.selectedIndex, "Unexpected selected option index."); 792 } 793 794 test.assertTrue(scriptResource instanceof WebInspector.Resource, 795 "Unexpected resource class."); 796 test.assertTrue(!!scriptResource.url, "Resource URL is null."); 797 test.assertTrue(scriptResource.url.search(scriptName + "$") !== -1, "Main HTML resource should be selected."); 798 799 var scriptsPanel = WebInspector.panels.scripts; 800 801 var view = scriptsPanel.visibleView; 802 test.assertTrue(view instanceof WebInspector.SourceView); 803 804 if (!view.sourceFrame._loaded) { 805 test.addSniffer(view, "_sourceFrameSetupFinished", function(event) { 806 callback(view, scriptResource.url); 807 }); 808 } else 809 callback(view, scriptResource.url); 810 }; 811 812 813 /* 814 * Evaluates the code in the console as if user typed it manually and invokes 815 * the callback when the result message is received and added to the console. 816 * @param {string} code 817 * @param {function(string)} callback 818 */ 819 TestSuite.prototype.evaluateInConsole_ = function(code, callback) 820 { 821 WebInspector.console.visible = true; 822 WebInspector.console.prompt.text = code; 823 WebInspector.console.promptElement.dispatchEvent( TestSuite.createKeyEvent("Enter")); 824 825 this.addSniffer(WebInspector.ConsoleView.prototype, "addMessage", 826 function(commandResult) { 827 callback(commandResult.toMessageElement().textContent); 828 }); 829 }; 830 831 832 /* 833 * Waits for "setbreakpoint" response, checks that corresponding breakpoint 834 * was successfully set and invokes the callback if it was. 835 * @param {string} scriptUrl 836 * @param {number} breakpointLine 837 * @param {function()} callback 838 */ 839 TestSuite.prototype.waitForSetBreakpointResponse_ = function(scriptUrl, breakpointLine, callback) 840 { 841 var test = this; 842 test.addSniffer( 843 devtools.DebuggerAgent.prototype, 844 "handleSetBreakpointResponse_", 845 function(msg) { 846 var bps = this.urlToBreakpoints_[scriptUrl]; 847 test.assertTrue(!!bps, "No breakpoints for line " + breakpointLine); 848 var line = devtools.DebuggerAgent.webkitToV8LineNumber_(breakpointLine); 849 test.assertTrue(!!bps[line].getV8Id(), "Breakpoint id was not assigned."); 850 callback(); 851 }); 852 }; 853 854 855 /** 856 * Tests eval on call frame. 857 */ 858 TestSuite.prototype.testEvalOnCallFrame = function() 859 { 860 this.showPanel("scripts"); 861 862 var breakpointLine = 16; 863 864 var test = this; 865 this.addSniffer(devtools.DebuggerAgent.prototype, "handleScriptsResponse_", 866 function(msg) { 867 test.showMainPageScriptSource_( 868 "debugger_test_page.html", 869 function(view, url) { 870 view._addBreakpoint(breakpointLine); 871 // Force v8 execution. 872 RemoteDebuggerAgent.processDebugCommands(); 873 test.waitForSetBreakpointResponse_(url, breakpointLine, setBreakpointCallback); 874 }); 875 }); 876 877 function setBreakpointCallback() { 878 // Since breakpoints are ignored in evals' calculate() function is 879 // execute after zero-timeout so that the breakpoint is hit. 880 test.evaluateInConsole_( 881 'setTimeout("calculate(123)" , 0)', 882 function(resultText) { 883 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText); 884 waitForBreakpointHit(); 885 }); 886 } 887 888 function waitForBreakpointHit() { 889 test.addSniffer( 890 devtools.DebuggerAgent.prototype, 891 "handleBacktraceResponse_", 892 function(msg) { 893 test.assertEquals(2, this.callFrames_.length, "Unexpected stack depth on the breakpoint. " + JSON.stringify(msg)); 894 test.assertEquals("calculate", this.callFrames_[0].functionName, "Unexpected top frame function."); 895 // Evaluate "e+1" where "e" is an argument of "calculate" function. 896 test.evaluateInConsole_( 897 "e+1", 898 function(resultText) { 899 test.assertEquals("124", resultText, 'Unexpected "e+1" value.'); 900 test.releaseControl(); 901 }); 902 }); 903 } 904 905 this.takeControl(); 906 }; 907 908 909 /** 910 * Tests that console auto completion works when script execution is paused. 911 */ 912 TestSuite.prototype.testCompletionOnPause = function() 913 { 914 this.showPanel("scripts"); 915 var test = this; 916 this._executeCodeWhenScriptsAreParsed("handleClick()", ["completion_on_pause.html$"]); 917 918 this._waitForScriptPause( 919 { 920 functionsOnStack: ["innerFunction", "handleClick", "(anonymous function)"], 921 lineNumber: 9, 922 lineText: " debugger;" 923 }, 924 showConsole); 925 926 function showConsole() { 927 test.addSniffer(WebInspector.console, "afterShow", testLocalsCompletion); 928 WebInspector.showConsole(); 929 } 930 931 function testLocalsCompletion() { 932 checkCompletions("th", ["parameter1", "closureLocal", "p", "createClosureLocal"], testThisCompletion); 933 } 934 935 function testThisCompletion() { 936 checkCompletions("this.", ["field1", "field2", "m"], testFieldCompletion); 937 } 938 939 function testFieldCompletion() { 940 checkCompletions("this.field1.", ["id", "name"], function() { test.releaseControl(); }); 941 } 942 943 function checkCompletions(expression, expectedProperties, callback) { 944 test.addSniffer(WebInspector.console, "_reportCompletions", 945 function(bestMatchOnly, completionsReadyCallback, dotNotation, bracketNotation, prefix, result, isException) { 946 test.assertTrue(!isException, "Exception while collecting completions"); 947 for (var i = 0; i < expectedProperties.length; i++) { 948 var name = expectedProperties[i]; 949 test.assertTrue(result[name], "Name " + name + " not found among the completions: " + JSON.stringify(result)); 950 } 951 setTimeout(callback, 0); 952 }); 953 WebInspector.console.prompt.text = expression; 954 WebInspector.console.prompt.autoCompleteSoon(); 955 } 956 957 this.takeControl(); 958 }; 959 960 961 /** 962 * Tests that inspected page doesn't hang on reload if it contains a syntax 963 * error and DevTools window is open. 964 */ 965 TestSuite.prototype.testAutoContinueOnSyntaxError = function() 966 { 967 this.showPanel("scripts"); 968 var test = this; 969 970 function checkScriptsList() { 971 var scriptSelect = document.getElementById("scripts-files"); 972 var options = scriptSelect.options; 973 // There should be only console API source (see 974 // InjectedScript._ensureCommandLineAPIInstalled) since the page script 975 // contains a syntax error. 976 for (var i = 0 ; i < options.length; i++) { 977 if (options[i].text.search("script_syntax_error.html$") !== -1) 978 test.fail("Script with syntax error should not be in the list of parsed scripts."); 979 } 980 } 981 982 this.addSniffer(devtools.DebuggerAgent.prototype, "handleScriptsResponse_", 983 function(msg) { 984 checkScriptsList(); 985 986 // Reload inspected page. 987 test.evaluateInConsole_( 988 "window.location.reload(true);", 989 function(resultText) { 990 test.assertEquals("undefined", resultText, "Unexpected result of reload()."); 991 waitForExceptionEvent(); 992 }); 993 }); 994 995 function waitForExceptionEvent() { 996 var exceptionCount = 0; 997 test.addSniffer( 998 devtools.DebuggerAgent.prototype, 999 "handleExceptionEvent_", 1000 function(msg) { 1001 exceptionCount++; 1002 test.assertEquals(1, exceptionCount, "Too many exceptions."); 1003 test.assertEquals(undefined, msg.getBody().script, "Unexpected exception: " + JSON.stringify(msg)); 1004 test.releaseControl(); 1005 }); 1006 1007 // Check that the script is not paused on parse error. 1008 test.addSniffer( 1009 WebInspector, 1010 "pausedScript", 1011 function(callFrames) { 1012 test.fail("Script execution should not pause on syntax error."); 1013 }); 1014 } 1015 1016 this.takeControl(); 1017 }; 1018 1019 1020 /** 1021 * Checks current execution line against expectations. 1022 * @param {WebInspector.SourceFrame} sourceFrame 1023 * @param {number} lineNumber Expected line number 1024 * @param {string} lineContent Expected line text 1025 */ 1026 TestSuite.prototype._checkExecutionLine = function(sourceFrame, lineNumber, lineContent) 1027 { 1028 this.assertEquals(lineNumber, sourceFrame.executionLine, "Unexpected execution line number."); 1029 this.assertEquals(lineContent, sourceFrame._textModel.line(lineNumber - 1), "Unexpected execution line text."); 1030 } 1031 1032 1033 /** 1034 * Checks that all expected scripts are present in the scripts list 1035 * in the Scripts panel. 1036 * @param {Array.<string>} expected Regular expressions describing 1037 * expected script names. 1038 * @return {boolean} Whether all the scripts are in "scripts-files" select 1039 * box 1040 */ 1041 TestSuite.prototype._scriptsAreParsed = function(expected) 1042 { 1043 var scriptSelect = document.getElementById("scripts-files"); 1044 var options = scriptSelect.options; 1045 1046 // Check that at least all the expected scripts are present. 1047 var missing = expected.slice(0); 1048 for (var i = 0 ; i < options.length; i++) { 1049 for (var j = 0; j < missing.length; j++) { 1050 if (options[i].text.search(missing[j]) !== -1) { 1051 missing.splice(j, 1); 1052 break; 1053 } 1054 } 1055 } 1056 return missing.length === 0; 1057 }; 1058 1059 1060 /** 1061 * Waits for script pause, checks expectations, and invokes the callback. 1062 * @param {Object} expectations Dictionary of expectations 1063 * @param {function():void} callback 1064 */ 1065 TestSuite.prototype._waitForScriptPause = function(expectations, callback) 1066 { 1067 var test = this; 1068 // Wait until script is paused. 1069 test.addSniffer( 1070 WebInspector, 1071 "pausedScript", 1072 function(callFrames) { 1073 var functionsOnStack = []; 1074 for (var i = 0; i < callFrames.length; i++) 1075 functionsOnStack.push(callFrames[i].functionName); 1076 1077 test.assertEquals(expectations.functionsOnStack.join(","), functionsOnStack.join(","), "Unexpected stack."); 1078 1079 // Check that execution line where the script is paused is 1080 // expected one. 1081 test._checkSourceFrameWhenLoaded(expectations, callback); 1082 }); 1083 }; 1084 1085 1086 /** 1087 * Waits for current source frame to load, checks expectations, and invokes 1088 * the callback. 1089 * @param {Object} expectations Dictionary of expectations 1090 * @param {function():void} callback 1091 */ 1092 TestSuite.prototype._checkSourceFrameWhenLoaded = function(expectations, callback) 1093 { 1094 var test = this; 1095 1096 var frame = WebInspector.currentPanel.visibleView.sourceFrame; 1097 if (frame._loaded) 1098 checkExecLine(); 1099 else { 1100 setTimeout(function() { 1101 test._checkSourceFrameWhenLoaded(expectations, callback); 1102 }, 100); 1103 } 1104 function checkExecLine() { 1105 test._checkExecutionLine(frame, expectations.lineNumber, expectations.lineText); 1106 callback(); 1107 } 1108 }; 1109 1110 1111 /** 1112 * Performs sequence of steps. 1113 * @param {Array.<Object|Function>} Array [expectations1,action1,expectations2, 1114 * action2,...,actionN]. 1115 */ 1116 TestSuite.prototype._performSteps = function(actions) 1117 { 1118 var test = this; 1119 var i = 0; 1120 function doNextAction() { 1121 if (i > 0) 1122 actions[i++](); 1123 if (i < actions.length - 1) 1124 test._waitForScriptPause(actions[i++], doNextAction); 1125 } 1126 doNextAction(); 1127 }; 1128 1129 1130 /** 1131 * Waits until all the scripts are parsed and asynchronously executes the code 1132 * in the inspected page. 1133 */ 1134 TestSuite.prototype._executeCodeWhenScriptsAreParsed = function(code, expectedScripts) 1135 { 1136 var test = this; 1137 1138 function executeFunctionInInspectedPage() { 1139 // Since breakpoints are ignored in evals' calculate() function is 1140 // execute after zero-timeout so that the breakpoint is hit. 1141 test.evaluateInConsole_( 1142 'setTimeout("' + code + '" , 0)', 1143 function(resultText) { 1144 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText); 1145 }); 1146 } 1147 1148 test._waitUntilScriptsAreParsed(expectedScripts, executeFunctionInInspectedPage); 1149 }; 1150 1151 1152 /** 1153 * Waits until all the scripts are parsed and invokes the callback. 1154 */ 1155 TestSuite.prototype._waitUntilScriptsAreParsed = function(expectedScripts, callback) 1156 { 1157 var test = this; 1158 1159 function waitForAllScripts() { 1160 if (test._scriptsAreParsed(expectedScripts)) 1161 callback(); 1162 else 1163 test.addSniffer(WebInspector, "parsedScriptSource", waitForAllScripts); 1164 } 1165 1166 waitForAllScripts(); 1167 }; 1168 1169 1170 /** 1171 * Waits until all debugger scripts are parsed and executes "a()" in the 1172 * inspected page. 1173 */ 1174 TestSuite.prototype._executeFunctionForStepTest = function() 1175 { 1176 this._executeCodeWhenScriptsAreParsed("a()", ["debugger_step.html$", "debugger_step.js$"]); 1177 }; 1178 1179 1180 /** 1181 * Tests step over in the debugger. 1182 */ 1183 TestSuite.prototype.testStepOver = function() 1184 { 1185 this.showPanel("scripts"); 1186 var test = this; 1187 1188 this._executeFunctionForStepTest(); 1189 1190 this._performSteps([ 1191 { 1192 functionsOnStack: ["d","a","(anonymous function)"], 1193 lineNumber: 3, 1194 lineText: " debugger;" 1195 }, 1196 function() { 1197 document.getElementById("scripts-step-over").click(); 1198 }, 1199 { 1200 functionsOnStack: ["d","a","(anonymous function)"], 1201 lineNumber: 5, 1202 lineText: " var y = fact(10);" 1203 }, 1204 function() { 1205 document.getElementById("scripts-step-over").click(); 1206 }, 1207 { 1208 functionsOnStack: ["d","a","(anonymous function)"], 1209 lineNumber: 6, 1210 lineText: " return y;" 1211 }, 1212 function() { 1213 test.releaseControl(); 1214 } 1215 ]); 1216 1217 test.takeControl(); 1218 }; 1219 1220 1221 /** 1222 * Tests step out in the debugger. 1223 */ 1224 TestSuite.prototype.testStepOut = function() 1225 { 1226 this.showPanel("scripts"); 1227 var test = this; 1228 1229 this._executeFunctionForStepTest(); 1230 1231 this._performSteps([ 1232 { 1233 functionsOnStack: ["d","a","(anonymous function)"], 1234 lineNumber: 3, 1235 lineText: " debugger;" 1236 }, 1237 function() { 1238 document.getElementById("scripts-step-out").click(); 1239 }, 1240 { 1241 functionsOnStack: ["a","(anonymous function)"], 1242 lineNumber: 8, 1243 lineText: " printResult(result);" 1244 }, 1245 function() { 1246 test.releaseControl(); 1247 } 1248 ]); 1249 1250 test.takeControl(); 1251 }; 1252 1253 1254 /** 1255 * Tests step in in the debugger. 1256 */ 1257 TestSuite.prototype.testStepIn = function() 1258 { 1259 this.showPanel("scripts"); 1260 var test = this; 1261 1262 this._executeFunctionForStepTest(); 1263 1264 this._performSteps([ 1265 { 1266 functionsOnStack: ["d","a","(anonymous function)"], 1267 lineNumber: 3, 1268 lineText: " debugger;" 1269 }, 1270 function() { 1271 document.getElementById("scripts-step-over").click(); 1272 }, 1273 { 1274 functionsOnStack: ["d","a","(anonymous function)"], 1275 lineNumber: 5, 1276 lineText: " var y = fact(10);" 1277 }, 1278 function() { 1279 document.getElementById("scripts-step-into").click(); 1280 }, 1281 { 1282 functionsOnStack: ["fact","d","a","(anonymous function)"], 1283 lineNumber: 15, 1284 lineText: " return r;" 1285 }, 1286 function() { 1287 test.releaseControl(); 1288 } 1289 ]); 1290 1291 test.takeControl(); 1292 }; 1293 1294 1295 /** 1296 * Gets a XPathResult matching given xpath. 1297 * @param {string} xpath 1298 * @param {number} resultType 1299 * @param {Node} opt_ancestor Context node. If not specified documentElement 1300 * will be used 1301 * @return {XPathResult} Type of returned value is determined by "resultType" parameter 1302 */ 1303 1304 TestSuite.prototype._evaluateXpath = function(xpath, resultType, opt_ancestor) 1305 { 1306 if (!opt_ancestor) 1307 opt_ancestor = document.documentElement; 1308 try { 1309 return document.evaluate(xpath, opt_ancestor, null, resultType, null); 1310 } catch(e) { 1311 this.fail('Error in expression: "' + xpath + '".' + e); 1312 } 1313 }; 1314 1315 1316 /** 1317 * Gets first Node matching given xpath. 1318 * @param {string} xpath 1319 * @param {Node} opt_ancestor Context node. If not specified documentElement 1320 * will be used 1321 * @return {?Node} 1322 */ 1323 TestSuite.prototype._findNode = function(xpath, opt_ancestor) 1324 { 1325 var result = this._evaluateXpath(xpath, XPathResult.FIRST_ORDERED_NODE_TYPE, opt_ancestor).singleNodeValue; 1326 this.assertTrue(!!result, "Cannot find node on path: " + xpath); 1327 return result; 1328 }; 1329 1330 1331 /** 1332 * Gets a text matching given xpath. 1333 * @param {string} xpath 1334 * @param {Node} opt_ancestor Context node. If not specified documentElement 1335 * will be used 1336 * @return {?string} 1337 */ 1338 TestSuite.prototype._findText = function(xpath, opt_ancestor) 1339 { 1340 var result = this._evaluateXpath(xpath, XPathResult.STRING_TYPE, opt_ancestor).stringValue; 1341 this.assertTrue(!!result, "Cannot find text on path: " + xpath); 1342 return result; 1343 }; 1344 1345 1346 /** 1347 * Gets an iterator over nodes matching given xpath. 1348 * @param {string} xpath 1349 * @param {Node} opt_ancestor Context node. If not specified, documentElement 1350 * will be used 1351 * @return {XPathResult} Iterator over the nodes 1352 */ 1353 TestSuite.prototype._nodeIterator = function(xpath, opt_ancestor) 1354 { 1355 return this._evaluateXpath(xpath, XPathResult.ORDERED_NODE_ITERATOR_TYPE, opt_ancestor); 1356 }; 1357 1358 1359 /** 1360 * Checks the scopeSectionDiv against the expectations. 1361 * @param {Node} scopeSectionDiv The section div 1362 * @param {Object} expectations Expectations dictionary 1363 */ 1364 TestSuite.prototype._checkScopeSectionDiv = function(scopeSectionDiv, expectations) 1365 { 1366 var scopeTitle = this._findText('./div[@class="header"]/div[@class="title"]/text()', scopeSectionDiv); 1367 this.assertEquals(expectations.title, scopeTitle, "Unexpected scope section title."); 1368 if (!expectations.properties) 1369 return; 1370 this.assertTrue(scopeSectionDiv.hasStyleClass("expanded"), 'Section "' + scopeTitle + '" is collapsed.'); 1371 1372 var propertyIt = this._nodeIterator("./ol/li", scopeSectionDiv); 1373 var propertyLi; 1374 var foundProps = []; 1375 while (propertyLi = propertyIt.iterateNext()) { 1376 var name = this._findText('./span[@class="name"]/text()', propertyLi); 1377 var value = this._findText('./span[@class="value"]/text()', propertyLi); 1378 this.assertTrue(!!name, 'Invalid variable name: "' + name + '"'); 1379 this.assertTrue(name in expectations.properties, "Unexpected property: " + name); 1380 this.assertEquals(expectations.properties[name], value, 'Unexpected "' + name + '" property value.'); 1381 delete expectations.properties[name]; 1382 foundProps.push(name + " = " + value); 1383 } 1384 1385 // Check that all expected properties were found. 1386 for (var p in expectations.properties) 1387 this.fail('Property "' + p + '" was not found in scope "' + scopeTitle + '". Found properties: "' + foundProps.join(",") + '"'); 1388 }; 1389 1390 1391 /** 1392 * Expands scope sections matching the filter and invokes the callback on 1393 * success. 1394 * @param {function(WebInspector.ObjectPropertiesSection, number):boolean} 1395 * filter 1396 * @param {Function} callback 1397 */ 1398 TestSuite.prototype._expandScopeSections = function(filter, callback) 1399 { 1400 var sections = WebInspector.currentPanel.sidebarPanes.scopechain.sections; 1401 1402 var toBeUpdatedCount = 0; 1403 function updateListener() { 1404 --toBeUpdatedCount; 1405 if (toBeUpdatedCount === 0) { 1406 // Report when all scopes are expanded and populated. 1407 callback(); 1408 } 1409 } 1410 1411 // Global scope is always the last one. 1412 for (var i = 0; i < sections.length - 1; i++) { 1413 var section = sections[i]; 1414 if (!filter(sections, i)) 1415 continue; 1416 ++toBeUpdatedCount; 1417 var populated = section.populated; 1418 1419 this._hookGetPropertiesCallback(updateListener, 1420 function() { 1421 section.expand(); 1422 if (populated) { 1423 // Make sure "updateProperties" callback will be called at least once 1424 // after it was overridden. 1425 section.update(); 1426 } 1427 }); 1428 } 1429 }; 1430 1431 1432 /** 1433 * Tests that scopes can be expanded and contain expected data. 1434 */ 1435 TestSuite.prototype.testExpandScope = function() 1436 { 1437 this.showPanel("scripts"); 1438 var test = this; 1439 1440 this._executeCodeWhenScriptsAreParsed("handleClick()", ["debugger_closure.html$"]); 1441 1442 this._waitForScriptPause( 1443 { 1444 functionsOnStack: ["innerFunction", "handleClick", "(anonymous function)"], 1445 lineNumber: 8, 1446 lineText: " debugger;" 1447 }, 1448 expandAllSectionsExceptGlobal); 1449 1450 // Expanding Global scope takes for too long so we skeep it. 1451 function expandAllSectionsExceptGlobal() { 1452 test._expandScopeSections(function(sections, i) { 1453 return i < sections.length - 1; 1454 }, 1455 examineScopes /* When all scopes are expanded and populated check them. */); 1456 } 1457 1458 // Check scope sections contents. 1459 function examineScopes() { 1460 var scopeVariablesSection = test._findNode('//div[@id="scripts-sidebar"]/div[div[@class="title"]/text()="Scope Variables"]'); 1461 var expectedScopes = [ 1462 { 1463 title: "Local", 1464 properties: { 1465 x:"2009", 1466 innerFunctionLocalVar:"2011", 1467 "this": "global", 1468 } 1469 }, 1470 { 1471 title: "Closure", 1472 properties: { 1473 n:"TextParam", 1474 makeClosureLocalVar:"local.TextParam", 1475 } 1476 }, 1477 { 1478 title: "Global", 1479 }, 1480 ]; 1481 var it = test._nodeIterator('./div[@class="body"]/div', scopeVariablesSection); 1482 var scopeIndex = 0; 1483 var scopeDiv; 1484 while (scopeDiv = it.iterateNext()) { 1485 test.assertTrue(scopeIndex < expectedScopes.length, "Too many scopes."); 1486 test._checkScopeSectionDiv(scopeDiv, expectedScopes[scopeIndex]); 1487 ++scopeIndex; 1488 } 1489 test.assertEquals(expectedScopes.length, scopeIndex, "Unexpected number of scopes."); 1490 1491 test.releaseControl(); 1492 } 1493 1494 test.takeControl(); 1495 }; 1496 1497 1498 /** 1499 * Returns child tree element for a property with given name. 1500 * @param {TreeElement} parent Parent tree element. 1501 * @param {string} childName 1502 * @param {string} objectPath Path to the object. Will be printed in the case 1503 * of failure. 1504 * @return {TreeElement} 1505 */ 1506 TestSuite.prototype._findChildProperty = function(parent, childName, objectPath) 1507 { 1508 var children = parent.children; 1509 for (var i = 0; i < children.length; i++) { 1510 var treeElement = children[i]; 1511 var property = treeElement.property; 1512 if (property.name === childName) 1513 return treeElement; 1514 } 1515 this.fail('Cannot find property "' + childName + '" in ' + objectPath); 1516 }; 1517 1518 1519 /** 1520 * Executes the 'code' with InjectedScriptAccess.getProperties overriden 1521 * so that all callbacks passed to InjectedScriptAccess.getProperties are 1522 * extended with the "hook". 1523 * @param {Function} hook The hook function. 1524 * @param {Function} code A code snippet to be executed. 1525 */ 1526 TestSuite.prototype._hookGetPropertiesCallback = function(hook, code) 1527 { 1528 var accessor = InjectedScriptAccess.prototype; 1529 var orig = accessor.getProperties; 1530 accessor.getProperties = function(objectProxy, ignoreHasOwnProperty, abbreviate, callback) { 1531 orig.call(this, objectProxy, ignoreHasOwnProperty, abbreviate, 1532 function() { 1533 callback.apply(this, arguments); 1534 hook(); 1535 }); 1536 }; 1537 try { 1538 code(); 1539 } finally { 1540 accessor.getProperties = orig; 1541 } 1542 }; 1543 1544 1545 /** 1546 * Tests that all elements in prototype chain of an object have expected 1547 * intrinic proprties(__proto__, constructor, prototype). 1548 */ 1549 TestSuite.prototype.testDebugIntrinsicProperties = function() 1550 { 1551 this.showPanel("scripts"); 1552 var test = this; 1553 1554 this._executeCodeWhenScriptsAreParsed("handleClick()", ["debugger_intrinsic_properties.html$"]); 1555 1556 this._waitForScriptPause( 1557 { 1558 functionsOnStack: ["callDebugger", "handleClick", "(anonymous function)"], 1559 lineNumber: 29, 1560 lineText: " debugger;" 1561 }, 1562 expandLocalScope); 1563 1564 var localScopeSection = null; 1565 function expandLocalScope() { 1566 test._expandScopeSections(function(sections, i) { 1567 if (i === 0) { 1568 test.assertTrue(sections[i].object.isLocal, "Scope #0 is not Local."); 1569 localScopeSection = sections[i]; 1570 return true; 1571 } 1572 return false; 1573 }, 1574 examineLocalScope); 1575 } 1576 1577 function examineLocalScope() { 1578 var scopeExpectations = [ 1579 "a", "Object", [ 1580 "constructor", "function Child()", [ 1581 "constructor", "function Function()", null, 1582 "name", "Child", null, 1583 "prototype", "Object", [ 1584 "childProtoField", 21, null 1585 ] 1586 ], 1587 1588 "__proto__", "Object", [ 1589 "__proto__", "Object", [ 1590 "__proto__", "Object", [ 1591 "__proto__", "null", null, 1592 "constructor", "function Object()", null, 1593 ], 1594 "constructor", "function Parent()", [ 1595 "name", "Parent", null, 1596 "prototype", "Object", [ 1597 "parentProtoField", 11, null, 1598 ] 1599 ], 1600 "parentProtoField", 11, null, 1601 ], 1602 "constructor", "function Child()", null, 1603 "childProtoField", 21, null, 1604 ], 1605 1606 "parentField", 10, null, 1607 "childField", 20, null, 1608 ] 1609 ]; 1610 1611 checkProperty(localScopeSection.propertiesTreeOutline, "<Local Scope>", scopeExpectations); 1612 } 1613 1614 var propQueue = []; 1615 var index = 0; 1616 var expectedFinalIndex = 8; 1617 1618 function expandAndCheckNextProperty() { 1619 if (index === propQueue.length) { 1620 test.assertEquals(expectedFinalIndex, index, "Unexpected number of expanded objects."); 1621 test.releaseControl(); 1622 return; 1623 } 1624 1625 // Read next property data from the queue. 1626 var treeElement = propQueue[index].treeElement; 1627 var path = propQueue[index].path; 1628 var expectations = propQueue[index].expectations; 1629 index++; 1630 1631 // Expand the property. 1632 test._hookGetPropertiesCallback(function() { 1633 checkProperty(treeElement, path, expectations); 1634 }, 1635 function() { 1636 treeElement.expand(); 1637 }); 1638 } 1639 1640 function checkProperty(treeElement, path, expectations) { 1641 for (var i = 0; i < expectations.length; i += 3) { 1642 var name = expectations[i]; 1643 var description = expectations[i+1]; 1644 var value = expectations[i+2]; 1645 1646 var propertyPath = path + "." + name; 1647 var propertyTreeElement = test._findChildProperty(treeElement, name, path); 1648 test.assertTrue(propertyTreeElement, 'Property "' + propertyPath + '" not found.'); 1649 test.assertEquals(description, propertyTreeElement.property.value.description, 'Unexpected "' + propertyPath + '" description.'); 1650 if (value) { 1651 // Schedule property content check. 1652 propQueue.push({ 1653 treeElement: propertyTreeElement, 1654 path: propertyPath, 1655 expectations: value, 1656 }); 1657 } 1658 } 1659 // Check next property in the queue. 1660 expandAndCheckNextProperty(); 1661 } 1662 1663 test.takeControl(); 1664 }; 1665 1666 1667 /** 1668 * Tests "Pause" button will pause debugger when a snippet is evaluated. 1669 */ 1670 TestSuite.prototype.testPauseInEval = function() 1671 { 1672 this.showPanel("scripts"); 1673 1674 var test = this; 1675 1676 var pauseButton = document.getElementById("scripts-pause"); 1677 pauseButton.click(); 1678 1679 devtools.tools.evaluateJavaScript("fib(10)"); 1680 1681 this.addSniffer(WebInspector, "pausedScript", 1682 function() { 1683 test.releaseControl(); 1684 }); 1685 1686 test.takeControl(); 1687 }; 1688 1689 1690 /** 1691 * Key event with given key identifier. 1692 */ 1693 TestSuite.createKeyEvent = function(keyIdentifier) 1694 { 1695 var evt = document.createEvent("KeyboardEvent"); 1696 evt.initKeyboardEvent("keydown", true /* can bubble */, true /* can cancel */, null /* view */, keyIdentifier, ""); 1697 return evt; 1698 }; 1699 1700 1701 /** 1702 * Tests console eval. 1703 */ 1704 TestSuite.prototype.testConsoleEval = function() 1705 { 1706 var test = this; 1707 this.evaluateInConsole_("123", 1708 function(resultText) { 1709 test.assertEquals("123", resultText); 1710 test.releaseControl(); 1711 }); 1712 1713 this.takeControl(); 1714 }; 1715 1716 1717 /** 1718 * Tests console log. 1719 */ 1720 TestSuite.prototype.testConsoleLog = function() 1721 { 1722 WebInspector.console.visible = true; 1723 var messages = WebInspector.console.messages; 1724 var index = 0; 1725 1726 var test = this; 1727 var assertNext = function(line, message, opt_class, opt_count, opt_substr) { 1728 var elem = messages[index++].toMessageElement(); 1729 var clazz = elem.getAttribute("class"); 1730 var expectation = (opt_count || '') + 'console_test_page.html:' + line + message; 1731 if (opt_substr) 1732 test.assertContains(elem.textContent, expectation); 1733 else 1734 test.assertEquals(expectation, elem.textContent); 1735 if (opt_class) 1736 test.assertContains(clazz, "console-" + opt_class); 1737 }; 1738 1739 assertNext("5", "log", "log-level"); 1740 assertNext("7", "debug", "log-level"); 1741 assertNext("9", "info", "log-level"); 1742 assertNext("11", "warn", "warning-level"); 1743 assertNext("13", "error", "error-level"); 1744 assertNext("15", "Message format number 1, 2 and 3.5"); 1745 assertNext("17", "Message format for string"); 1746 assertNext("19", "Object Object"); 1747 assertNext("22", "repeated", "log-level", 5); 1748 assertNext("26", "count: 1"); 1749 assertNext("26", "count: 2"); 1750 assertNext("29", "group", "group-title"); 1751 index++; 1752 assertNext("33", "timer:", "log-level", "", true); 1753 assertNext("35", "1 2 3", "log-level"); 1754 assertNext("37", "HTMLDocument", "log-level"); 1755 assertNext("39", "<html>", "log-level", "", true); 1756 }; 1757 1758 1759 /** 1760 * Tests eval of global objects. 1761 */ 1762 TestSuite.prototype.testEvalGlobal = function() 1763 { 1764 WebInspector.console.visible = true; 1765 1766 var inputs = ["foo", "foobar"]; 1767 var expectations = ["foo", "fooValue", "foobar", "ReferenceError: foobar is not defined"]; 1768 1769 // Do not change code below - simply add inputs and expectations above. 1770 var initEval = function(input) { 1771 WebInspector.console.prompt.text = input; 1772 WebInspector.console.promptElement.dispatchEvent( TestSuite.createKeyEvent("Enter")); 1773 }; 1774 var test = this; 1775 var messagesCount = 0; 1776 var inputIndex = 0; 1777 this.addSniffer(WebInspector.ConsoleView.prototype, "addMessage", 1778 function(commandResult) { 1779 messagesCount++; 1780 if (messagesCount === expectations.length) { 1781 var messages = WebInspector.console.messages; 1782 for (var i = 0; i < expectations; ++i) { 1783 var elem = messages[i++].toMessageElement(); 1784 test.assertEquals(elem.textContent, expectations[i]); 1785 } 1786 test.releaseControl(); 1787 } else if (messagesCount % 2 === 0) 1788 initEval(inputs[inputIndex++]); 1789 }, true); 1790 1791 initEval(inputs[inputIndex++]); 1792 this.takeControl(); 1793 }; 1794 1795 1796 /** 1797 * Tests that Storage panel can be open and that local DOM storage is added 1798 * to the panel. 1799 */ 1800 TestSuite.prototype.testShowStoragePanel = function() 1801 { 1802 var test = this; 1803 this.addSniffer(WebInspector.panels.storage, "addDOMStorage", 1804 function(storage) { 1805 var orig = storage.getEntries; 1806 storage.getEntries = function(callback) { 1807 orig.call(this, function(entries) { 1808 callback(entries); 1809 test.releaseControl(); 1810 }); 1811 }; 1812 try { 1813 WebInspector.currentPanel.selectDOMStorage(storage.id); 1814 storage.getEntries = orig; 1815 } catch (e) { 1816 test.fail("Exception in selectDOMStorage: " + e); 1817 } 1818 }); 1819 this.showPanel("storage"); 1820 1821 // Access localStorage so that it's pushed to the frontend. 1822 this.evaluateInConsole_( 1823 'setTimeout("localStorage.x = 10" , 0)', 1824 function(resultText) { 1825 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText); 1826 }); 1827 1828 // Wait until DOM storage is added to the panel. 1829 this.takeControl(); 1830 }; 1831 1832 1833 /** 1834 * Test runner for the test suite. 1835 */ 1836 var uiTests = {}; 1837 1838 1839 /** 1840 * Run each test from the test suit on a fresh instance of the suite. 1841 */ 1842 uiTests.runAllTests = function() 1843 { 1844 // For debugging purposes. 1845 for (var name in TestSuite.prototype) { 1846 if (name.substring(0, 4) === "test" && typeof TestSuite.prototype[name] === "function") 1847 uiTests.runTest(name); 1848 } 1849 }; 1850 1851 1852 /** 1853 * Run specified test on a fresh instance of the test suite. 1854 * @param {string} name Name of a test method from TestSuite class. 1855 */ 1856 uiTests.runTest = function(name) 1857 { 1858 new TestSuite().runTest(name); 1859 }; 1860 1861 1862 } 1863