Home | History | Annotate | Download | only in js
      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