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 40if (window.domAutomationController) { 41 42var ___interactiveUiTestsMode = true; 43 44/** 45 * Test suite for interactive UI tests. 46 * @constructor 47 */ 48TestSuite = 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 */ 59TestSuite.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 */ 74TestSuite.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 * 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. 88 */ 89TestSuite.prototype.assertTrue = function(value, opt_message) 90{ 91 this.assertEquals(true, !!value, opt_message); 92}; 93 94 95/** 96 * HasKey assertion tests that object has given key. 97 * @param {!Object} object 98 * @param {string} key 99 */ 100TestSuite.prototype.assertHasKey = function(object, key) 101{ 102 if (!object.hasOwnProperty(key)) 103 this.fail("Expected object to contain key '" + key + "'"); 104}; 105 106 107/** 108 * Contains assertion tests that string contains substring. 109 * @param {string} string !Outer. 110 * @param {string} substring !Inner. 111 */ 112TestSuite.prototype.assertContains = function(string, substring) 113{ 114 if (string.indexOf(substring) === -1) 115 this.fail("Expected to: '" + string + "' to contain '" + substring + "'"); 116}; 117 118 119/** 120 * Takes control over execution. 121 */ 122TestSuite.prototype.takeControl = function() 123{ 124 this.controlTaken_ = true; 125 // Set up guard timer. 126 var self = this; 127 this.timerId_ = setTimeout(function() { 128 self.reportFailure_("Timeout exceeded: 20 sec"); 129 }, 20000); 130}; 131 132 133/** 134 * Releases control over execution. 135 */ 136TestSuite.prototype.releaseControl = function() 137{ 138 if (this.timerId_ !== -1) { 139 clearTimeout(this.timerId_); 140 this.timerId_ = -1; 141 } 142 this.reportOk_(); 143}; 144 145 146/** 147 * Async tests use this one to report that they are completed. 148 */ 149TestSuite.prototype.reportOk_ = function() 150{ 151 window.domAutomationController.send("[OK]"); 152}; 153 154 155/** 156 * Async tests use this one to report failures. 157 */ 158TestSuite.prototype.reportFailure_ = function(error) 159{ 160 if (this.timerId_ !== -1) { 161 clearTimeout(this.timerId_); 162 this.timerId_ = -1; 163 } 164 window.domAutomationController.send("[FAILED] " + error); 165}; 166 167 168/** 169 * Runs all global functions starting with "test" as unit tests. 170 */ 171TestSuite.prototype.runTest = function(testName) 172{ 173 try { 174 this[testName](); 175 if (!this.controlTaken_) 176 this.reportOk_(); 177 } catch (e) { 178 this.reportFailure_(e); 179 } 180}; 181 182 183/** 184 * @param {string} panelName !Name of the panel to show. 185 */ 186TestSuite.prototype.showPanel = function(panelName) 187{ 188 // Open Scripts panel. 189 var button = document.getElementById("tab-" + panelName); 190 button.selectTabForTest(); 191 this.assertEquals(WebInspector.panels[panelName], WebInspector.inspectorView.currentPanel()); 192}; 193 194 195/** 196 * Overrides the method with specified name until it's called first time. 197 * @param {!Object} receiver !An object whose method to override. 198 * @param {string} methodName !Name of the method to override. 199 * @param {!Function} override !A function that should be called right after the 200 * overriden method returns. 201 * @param {boolean} opt_sticky !Whether restore original method after first run 202 * or not. 203 */ 204TestSuite.prototype.addSniffer = function(receiver, methodName, override, opt_sticky) 205{ 206 var orig = receiver[methodName]; 207 if (typeof orig !== "function") 208 this.fail("Cannot find method to override: " + methodName); 209 var test = this; 210 receiver[methodName] = function(var_args) { 211 try { 212 var result = orig.apply(this, arguments); 213 } finally { 214 if (!opt_sticky) 215 receiver[methodName] = orig; 216 } 217 // In case of exception the override won't be called. 218 try { 219 override.apply(this, arguments); 220 } catch (e) { 221 test.fail("Exception in overriden method '" + methodName + "': " + e); 222 } 223 return result; 224 }; 225}; 226 227 228TestSuite.prototype.testEnableResourcesTab = function() 229{ 230 // FIXME once reference is removed downstream. 231} 232 233TestSuite.prototype.testCompletionOnPause = function() 234{ 235 // FIXME once reference is removed downstream. 236} 237 238// UI Tests 239 240 241/** 242 * Tests that scripts tab can be open and populated with inspected scripts. 243 */ 244TestSuite.prototype.testShowScriptsTab = function() 245{ 246 this.showPanel("sources"); 247 var test = this; 248 // There should be at least main page script. 249 this._waitUntilScriptsAreParsed(["debugger_test_page.html"], 250 function() { 251 test.releaseControl(); 252 }); 253 // Wait until all scripts are added to the debugger. 254 this.takeControl(); 255}; 256 257 258/** 259 * Tests that scripts tab is populated with inspected scripts even if it 260 * hadn't been shown by the moment inspected paged refreshed. 261 * @see http://crbug.com/26312 262 */ 263TestSuite.prototype.testScriptsTabIsPopulatedOnInspectedPageRefresh = function() 264{ 265 var test = this; 266 this.assertEquals(WebInspector.panels.elements, WebInspector.inspectorView.currentPanel(), "Elements panel should be current one."); 267 268 WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, waitUntilScriptIsParsed); 269 270 // Reload inspected page. It will reset the debugger agent. 271 test.evaluateInConsole_("window.location.reload(true);", function(resultText) {}); 272 273 function waitUntilScriptIsParsed() 274 { 275 WebInspector.debuggerModel.removeEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, waitUntilScriptIsParsed); 276 test.showPanel("sources"); 277 test._waitUntilScriptsAreParsed(["debugger_test_page.html"], 278 function() { 279 test.releaseControl(); 280 }); 281 } 282 283 // Wait until all scripts are added to the debugger. 284 this.takeControl(); 285}; 286 287 288/** 289 * Tests that scripts list contains content scripts. 290 */ 291TestSuite.prototype.testContentScriptIsPresent = function() 292{ 293 this.showPanel("sources"); 294 var test = this; 295 296 test._waitUntilScriptsAreParsed( 297 ["page_with_content_script.html", "simple_content_script.js"], 298 function() { 299 test.releaseControl(); 300 }); 301 302 // Wait until all scripts are added to the debugger. 303 this.takeControl(); 304}; 305 306 307/** 308 * Tests that scripts are not duplicaed on Scripts tab switch. 309 */ 310TestSuite.prototype.testNoScriptDuplicatesOnPanelSwitch = function() 311{ 312 var test = this; 313 314 // There should be two scripts: one for the main page and another 315 // one which is source of console API(see 316 // InjectedScript._ensureCommandLineAPIInstalled). 317 var expectedScriptsCount = 2; 318 var parsedScripts = []; 319 320 this.showPanel("sources"); 321 322 function switchToElementsTab() { 323 test.showPanel("elements"); 324 setTimeout(switchToScriptsTab, 0); 325 } 326 327 function switchToScriptsTab() { 328 test.showPanel("sources"); 329 setTimeout(checkScriptsPanel, 0); 330 } 331 332 function checkScriptsPanel() { 333 test.assertTrue(test._scriptsAreParsed(["debugger_test_page.html"]), "Some scripts are missing."); 334 checkNoDuplicates(); 335 test.releaseControl(); 336 } 337 338 function checkNoDuplicates() { 339 var uiSourceCodes = test.nonAnonymousUISourceCodes_(); 340 for (var i = 0; i < uiSourceCodes.length; i++) { 341 var scriptName = uiSourceCodes[i].url; 342 for (var j = i + 1; j < uiSourceCodes.length; j++) 343 test.assertTrue(scriptName !== uiSourceCodes[j].url, "Found script duplicates: " + test.uiSourceCodesToString_(uiSourceCodes)); 344 } 345 } 346 347 test._waitUntilScriptsAreParsed( 348 ["debugger_test_page.html"], 349 function() { 350 checkNoDuplicates(); 351 setTimeout(switchToElementsTab, 0); 352 }); 353 354 355 // Wait until all scripts are added to the debugger. 356 this.takeControl(); 357}; 358 359 360// Tests that debugger works correctly if pause event occurs when DevTools 361// frontend is being loaded. 362TestSuite.prototype.testPauseWhenLoadingDevTools = function() 363{ 364 this.showPanel("sources"); 365 366 // Script execution can already be paused. 367 if (WebInspector.debuggerModel.debuggerPausedDetails) 368 return; 369 370 this._waitForScriptPause(this.releaseControl.bind(this)); 371 this.takeControl(); 372}; 373 374 375// Tests that pressing "Pause" will pause script execution if the script 376// is already running. 377TestSuite.prototype.testPauseWhenScriptIsRunning = function() 378{ 379 this.showPanel("sources"); 380 381 this.evaluateInConsole_( 382 'setTimeout("handleClick()" , 0)', 383 didEvaluateInConsole.bind(this)); 384 385 function didEvaluateInConsole(resultText) { 386 this.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText); 387 // Wait for some time to make sure that inspected page is running the 388 // infinite loop. 389 setTimeout(testScriptPause.bind(this), 300); 390 } 391 392 function testScriptPause() { 393 // The script should be in infinite loop. Click "Pause" button to 394 // pause it and wait for the result. 395 WebInspector.panels.sources._pauseButton.element.click(); 396 397 this._waitForScriptPause(this.releaseControl.bind(this)); 398 } 399 400 this.takeControl(); 401}; 402 403 404/** 405 * Tests network size. 406 */ 407TestSuite.prototype.testNetworkSize = function() 408{ 409 var test = this; 410 411 function finishResource(resource, finishTime) 412 { 413 test.assertEquals(219, resource.transferSize, "Incorrect total encoded data length"); 414 test.assertEquals(25, resource.resourceSize, "Incorrect total data length"); 415 test.releaseControl(); 416 } 417 418 this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishNetworkRequest", finishResource); 419 420 // Reload inspected page to sniff network events 421 test.evaluateInConsole_("window.location.reload(true);", function(resultText) {}); 422 423 this.takeControl(); 424}; 425 426 427/** 428 * Tests network sync size. 429 */ 430TestSuite.prototype.testNetworkSyncSize = function() 431{ 432 var test = this; 433 434 function finishResource(resource, finishTime) 435 { 436 test.assertEquals(219, resource.transferSize, "Incorrect total encoded data length"); 437 test.assertEquals(25, resource.resourceSize, "Incorrect total data length"); 438 test.releaseControl(); 439 } 440 441 this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishNetworkRequest", finishResource); 442 443 // Send synchronous XHR to sniff network events 444 test.evaluateInConsole_("var xhr = new XMLHttpRequest(); xhr.open(\"GET\", \"chunked\", false); xhr.send(null);", function() {}); 445 446 this.takeControl(); 447}; 448 449 450/** 451 * Tests network raw headers text. 452 */ 453TestSuite.prototype.testNetworkRawHeadersText = function() 454{ 455 var test = this; 456 457 function finishResource(resource, finishTime) 458 { 459 if (!resource.responseHeadersText) 460 test.fail("Failure: resource does not have response headers text"); 461 test.assertEquals(164, resource.responseHeadersText.length, "Incorrect response headers text length"); 462 test.releaseControl(); 463 } 464 465 this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishNetworkRequest", finishResource); 466 467 // Reload inspected page to sniff network events 468 test.evaluateInConsole_("window.location.reload(true);", function(resultText) {}); 469 470 this.takeControl(); 471}; 472 473 474/** 475 * Tests network timing. 476 */ 477TestSuite.prototype.testNetworkTiming = function() 478{ 479 var test = this; 480 481 function finishResource(resource, finishTime) 482 { 483 // Setting relaxed expectations to reduce flakiness. 484 // Server sends headers after 100ms, then sends data during another 100ms. 485 // We expect these times to be measured at least as 70ms. 486 test.assertTrue(resource.timing.receiveHeadersEnd - resource.timing.connectStart >= 70, 487 "Time between receiveHeadersEnd and connectStart should be >=70ms, but was " + 488 "receiveHeadersEnd=" + resource.timing.receiveHeadersEnd + ", connectStart=" + resource.timing.connectStart + "."); 489 test.assertTrue(resource.responseReceivedTime - resource.startTime >= 0.07, 490 "Time between responseReceivedTime and startTime should be >=0.07s, but was " + 491 "responseReceivedTime=" + resource.responseReceivedTime + ", startTime=" + resource.startTime + "."); 492 test.assertTrue(resource.endTime - resource.startTime >= 0.14, 493 "Time between endTime and startTime should be >=0.14s, but was " + 494 "endtime=" + resource.endTime + ", startTime=" + resource.startTime + "."); 495 496 test.releaseControl(); 497 } 498 499 this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishNetworkRequest", finishResource); 500 501 // Reload inspected page to sniff network events 502 test.evaluateInConsole_("window.location.reload(true);", function(resultText) {}); 503 504 this.takeControl(); 505}; 506 507 508TestSuite.prototype.testConsoleOnNavigateBack = function() 509{ 510 if (WebInspector.console.messages.length === 1) 511 firstConsoleMessageReceived.call(this); 512 else 513 WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, firstConsoleMessageReceived, this); 514 515 function firstConsoleMessageReceived() { 516 WebInspector.console.removeEventListener(WebInspector.ConsoleModel.Events.MessageAdded, firstConsoleMessageReceived, this); 517 this.evaluateInConsole_("clickLink();", didClickLink.bind(this)); 518 } 519 520 function didClickLink() { 521 // Check that there are no new messages(command is not a message). 522 this.assertEquals(3, WebInspector.console.messages.length); 523 this.assertEquals(1, WebInspector.console.messages[0].totalRepeatCount); 524 this.evaluateInConsole_("history.back();", didNavigateBack.bind(this)); 525 } 526 527 function didNavigateBack() 528 { 529 // Make sure navigation completed and possible console messages were pushed. 530 this.evaluateInConsole_("void 0;", didCompleteNavigation.bind(this)); 531 } 532 533 function didCompleteNavigation() { 534 this.assertEquals(7, WebInspector.console.messages.length); 535 this.assertEquals(1, WebInspector.console.messages[0].totalRepeatCount); 536 this.releaseControl(); 537 } 538 539 this.takeControl(); 540}; 541 542 543TestSuite.prototype.testReattachAfterCrash = function() 544{ 545 this.evaluateInConsole_("1+1;", this.releaseControl.bind(this)); 546 this.takeControl(); 547}; 548 549 550TestSuite.prototype.testSharedWorker = function() 551{ 552 function didEvaluateInConsole(resultText) { 553 this.assertEquals("2011", resultText); 554 this.releaseControl(); 555 } 556 this.evaluateInConsole_("globalVar", didEvaluateInConsole.bind(this)); 557 this.takeControl(); 558}; 559 560 561TestSuite.prototype.testPauseInSharedWorkerInitialization = function() 562{ 563 if (WebInspector.debuggerModel.debuggerPausedDetails) 564 return; 565 this._waitForScriptPause(this.releaseControl.bind(this)); 566 this.takeControl(); 567}; 568 569/** 570 * Tests that timeline receives frame signals. 571 */ 572TestSuite.prototype.testTimelineFrames = function() 573{ 574 var test = this; 575 576 function step1() 577 { 578 test.recordTimeline(onTimelineRecorded); 579 test.evaluateInConsole_("runTest()", function(){}); 580 } 581 582 function onTimelineRecorded(records) 583 { 584 var frameCount = 0; 585 var recordsInFrame = {}; 586 587 for (var i = 0; i < records.length; ++i) { 588 var record = records[i]; 589 if (record.type !== "BeginFrame") { 590 recordsInFrame[record.type] = (recordsInFrame[record.type] || 0) + 1; 591 continue; 592 } 593 if (!frameCount++) 594 continue; 595 596 test.assertHasKey(recordsInFrame, "FireAnimationFrame"); 597 test.assertHasKey(recordsInFrame, "Layout"); 598 test.assertHasKey(recordsInFrame, "RecalculateStyles"); 599 test.assertHasKey(recordsInFrame, "Paint"); 600 recordsInFrame = {}; 601 } 602 test.assertTrue(frameCount >= 5, "Not enough frames"); 603 test.releaseControl(); 604 } 605 606 step1(); 607 test.takeControl(); 608} 609 610// Regression test for http://webk.it/97466 611TestSuite.prototype.testPageOverlayUpdate = function() 612{ 613 var test = this; 614 615 function populatePage() 616 { 617 var div1 = document.createElement("div"); 618 div1.id = "div1"; 619 // Force accelerated compositing. 620 div1.style.webkitTransform = "translateZ(0)"; 621 document.body.appendChild(div1); 622 var div2 = document.createElement("div"); 623 div2.id = "div2"; 624 document.body.appendChild(div2); 625 } 626 627 function step1() 628 { 629 test.evaluateInConsole_(populatePage.toString() + "; populatePage();" + 630 "inspect(document.getElementById('div1'))", function() {}); 631 WebInspector.notifications.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, step2); 632 } 633 634 function step2() 635 { 636 WebInspector.notifications.removeEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, step2); 637 test.recordTimeline(onTimelineRecorded); 638 setTimeout(step3, 500); 639 } 640 641 function step3() 642 { 643 test.evaluateInConsole_("inspect(document.getElementById('div2'))", function() {}); 644 WebInspector.notifications.addEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, step4); 645 } 646 647 function step4() 648 { 649 WebInspector.notifications.removeEventListener(WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged, step4); 650 test.stopTimeline(); 651 } 652 653 function onTimelineRecorded(records) 654 { 655 var types = {}; 656 for (var i = 0; i < records.length; ++i) 657 types[records[i].type] = (types[records[i].type] || 0) + 1; 658 659 var frameCount = types["BeginFrame"]; 660 // There should be at least two updates caused by selection of nodes. 661 test.assertTrue(frameCount >= 2, "Not enough DevTools overlay updates"); 662 // We normally expect up to 3 frames, but allow for a bit more in case 663 // of some unexpected invalidations. 664 test.assertTrue(frameCount < 6, "Too many updates caused by DevTools overlay"); 665 test.releaseControl(); 666 } 667 668 step1(); 669 this.takeControl(); 670} 671 672 673/** 674 * Records timeline till console.timeStamp("ready"), invokes callback with resulting records. 675 * @param {function(!Array.<!Object>)} callback 676 */ 677TestSuite.prototype.recordTimeline = function(callback) 678{ 679 var records = []; 680 var dispatchOnRecordType = {} 681 682 WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, addRecord); 683 WebInspector.timelineManager.start(); 684 685 function addRecord(event) 686 { 687 innerAddRecord(event.data); 688 } 689 690 function innerAddRecord(record) 691 { 692 records.push(record); 693 if (record.type === "TimeStamp" && record.data.message === "ready") 694 done(); 695 696 if (record.children) 697 record.children.forEach(innerAddRecord); 698 } 699 700 function done() 701 { 702 WebInspector.timelineManager.stop(); 703 WebInspector.timelineManager.removeEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded, addRecord); 704 callback(records); 705 } 706} 707 708 709TestSuite.prototype.stopTimeline = function() 710{ 711 this.evaluateInConsole_("console.timeStamp('ready')", function() {}); 712} 713 714TestSuite.prototype.waitForTestResultsInConsole = function() 715{ 716 var messages = WebInspector.console.messages; 717 for (var i = 0; i < messages.length; ++i) { 718 var text = messages[i].text; 719 if (text === "PASS") 720 return; 721 else if (/^FAIL/.test(text)) 722 this.fail(text); // This will throw. 723 } 724 // Neitwer PASS nor FAIL, so wait for more messages. 725 function onConsoleMessage(event) 726 { 727 var text = event.data.text; 728 if (text === "PASS") 729 this.releaseControl(); 730 else if (/^FAIL/.test(text)) 731 this.fail(text); 732 } 733 734 WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, onConsoleMessage, this); 735 this.takeControl(); 736}; 737 738TestSuite.prototype.checkLogAndErrorMessages = function() 739{ 740 var messages = WebInspector.console.messages; 741 742 var matchesCount = 0; 743 function validMessage(message) 744 { 745 if (message.text === "log" && message.level === WebInspector.ConsoleMessage.MessageLevel.Log) { 746 ++matchesCount; 747 return true; 748 } 749 750 if (message.text === "error" && message.level === WebInspector.ConsoleMessage.MessageLevel.Error) { 751 ++matchesCount; 752 return true; 753 } 754 return false; 755 } 756 757 for (var i = 0; i < messages.length; ++i) { 758 if (validMessage(messages[i])) 759 continue; 760 this.fail(messages[i].text + ":" + messages[i].level); // This will throw. 761 } 762 763 if (matchesCount === 2) 764 return; 765 766 // Wait for more messages. 767 function onConsoleMessage(event) 768 { 769 var message = event.data; 770 if (validMessage(message)) { 771 if (matchesCount === 2) { 772 this.releaseControl(); 773 return; 774 } 775 } else 776 this.fail(message.text + ":" + messages[i].level); 777 } 778 779 WebInspector.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, onConsoleMessage, this); 780 this.takeControl(); 781}; 782 783/** 784 * Serializes array of uiSourceCodes to string. 785 * @param {!Array.<!WebInspectorUISourceCode>} uiSourceCodes 786 * @return {string} 787 */ 788TestSuite.prototype.uiSourceCodesToString_ = function(uiSourceCodes) 789{ 790 var names = []; 791 for (var i = 0; i < uiSourceCodes.length; i++) 792 names.push('"' + uiSourceCodes[i].url + '"'); 793 return names.join(","); 794}; 795 796 797/** 798 * Returns all loaded non anonymous uiSourceCodes. 799 * @return {!Array.<!WebInspectorUISourceCode>} 800 */ 801TestSuite.prototype.nonAnonymousUISourceCodes_ = function() 802{ 803 function filterOutAnonymous(uiSourceCode) 804 { 805 return !!uiSourceCode.url; 806 } 807 808 function filterOutService(uiSourceCode) 809 { 810 return !uiSourceCode.project().isServiceProject(); 811 } 812 813 var uiSourceCodes = WebInspector.workspace.uiSourceCodes(); 814 uiSourceCodes = uiSourceCodes.filter(filterOutService); 815 return uiSourceCodes.filter(filterOutAnonymous); 816}; 817 818 819/* 820 * Evaluates the code in the console as if user typed it manually and invokes 821 * the callback when the result message is received and added to the console. 822 * @param {string} code 823 * @param {function(string)} callback 824 */ 825TestSuite.prototype.evaluateInConsole_ = function(code, callback) 826{ 827 WebInspector.showConsole(); 828 WebInspector.consoleView.prompt.text = code; 829 WebInspector.consoleView.promptElement.dispatchEvent(TestSuite.createKeyEvent("Enter")); 830 831 this.addSniffer(WebInspector.ConsoleView.prototype, "_showConsoleMessage", 832 function(messageIndex) { 833 var commandResult = WebInspector.console.messages[messageIndex]; 834 callback(commandResult.toMessageElement().textContent); 835 }); 836}; 837 838 839/** 840 * Checks that all expected scripts are present in the scripts list 841 * in the Scripts panel. 842 * @param {!Array.<string>} expected !Regular expressions describing 843 * expected script names. 844 * @return {boolean} Whether all the scripts are in "scripts-files" select 845 * box 846 */ 847TestSuite.prototype._scriptsAreParsed = function(expected) 848{ 849 var uiSourceCodes = this.nonAnonymousUISourceCodes_(); 850 // Check that at least all the expected scripts are present. 851 var missing = expected.slice(0); 852 for (var i = 0; i < uiSourceCodes.length; ++i) { 853 for (var j = 0; j < missing.length; ++j) { 854 if (uiSourceCodes[i].name().search(missing[j]) !== -1) { 855 missing.splice(j, 1); 856 break; 857 } 858 } 859 } 860 return missing.length === 0; 861}; 862 863 864/** 865 * Waits for script pause, checks expectations, and invokes the callback. 866 * @param {function():void} callback 867 */ 868TestSuite.prototype._waitForScriptPause = function(callback) 869{ 870 function pauseListener(event) { 871 WebInspector.debuggerModel.removeEventListener(WebInspector.DebuggerModel.Events.DebuggerPaused, pauseListener, this); 872 callback(); 873 } 874 WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerPaused, pauseListener, this); 875}; 876 877 878/** 879 * Waits until all the scripts are parsed and asynchronously executes the code 880 * in the inspected page. 881 */ 882TestSuite.prototype._executeCodeWhenScriptsAreParsed = function(code, expectedScripts) 883{ 884 var test = this; 885 886 function executeFunctionInInspectedPage() { 887 // Since breakpoints are ignored in evals' calculate() function is 888 // execute after zero-timeout so that the breakpoint is hit. 889 test.evaluateInConsole_( 890 'setTimeout("' + code + '" , 0)', 891 function(resultText) { 892 test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText + ". Code: " + code); 893 }); 894 } 895 896 test._waitUntilScriptsAreParsed(expectedScripts, executeFunctionInInspectedPage); 897}; 898 899 900/** 901 * Waits until all the scripts are parsed and invokes the callback. 902 */ 903TestSuite.prototype._waitUntilScriptsAreParsed = function(expectedScripts, callback) 904{ 905 var test = this; 906 907 function waitForAllScripts() { 908 if (test._scriptsAreParsed(expectedScripts)) 909 callback(); 910 else 911 test.addSniffer(WebInspector.panels.sources, "_addUISourceCode", waitForAllScripts); 912 } 913 914 waitForAllScripts(); 915}; 916 917 918/** 919 * Key event with given key identifier. 920 */ 921TestSuite.createKeyEvent = function(keyIdentifier) 922{ 923 var evt = document.createEvent("KeyboardEvent"); 924 evt.initKeyboardEvent("keydown", true /* can bubble */, true /* can cancel */, null /* view */, keyIdentifier, ""); 925 return evt; 926}; 927 928 929/** 930 * Test runner for the test suite. 931 */ 932var uiTests = {}; 933 934 935/** 936 * Run each test from the test suit on a fresh instance of the suite. 937 */ 938uiTests.runAllTests = function() 939{ 940 // For debugging purposes. 941 for (var name in TestSuite.prototype) { 942 if (name.substring(0, 4) === "test" && typeof TestSuite.prototype[name] === "function") 943 uiTests.runTest(name); 944 } 945}; 946 947 948/** 949 * Run specified test on a fresh instance of the test suite. 950 * @param {string} name Name of a test method from !TestSuite class. 951 */ 952uiTests.runTest = function(name) 953{ 954 if (uiTests._populatedInterface) 955 new TestSuite().runTest(name); 956 else 957 uiTests._pendingTestName = name; 958}; 959 960(function() { 961 962function runTests() 963{ 964 uiTests._populatedInterface = true; 965 var name = uiTests._pendingTestName; 966 delete uiTests._pendingTestName; 967 if (name) 968 new TestSuite().runTest(name); 969} 970 971var oldLoadCompleted = InspectorFrontendAPI.loadCompleted; 972InspectorFrontendAPI.loadCompleted = function() 973{ 974 oldLoadCompleted.call(InspectorFrontendAPI); 975 runTests(); 976} 977 978})(); 979 980} 981