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 *     overridden 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/**
228 * Waits for current throttler invocations, if any.
229 * @param {!WebInspector.Throttler} throttler
230 * @param {!function} callback
231 */
232TestSuite.prototype.waitForThrottler = function(throttler, callback)
233{
234    var test = this;
235    var scheduleShouldFail = true;
236    test.addSniffer(throttler, "schedule", onSchedule);
237
238    function hasSomethingScheduled()
239    {
240        return throttler._isRunningProcess || throttler._process;
241    }
242
243    function checkState()
244    {
245        if (!hasSomethingScheduled()) {
246            scheduleShouldFail = false;
247            callback();
248            return;
249        }
250
251        test.addSniffer(throttler, "_processCompletedForTests", checkState);
252    }
253
254    function onSchedule()
255    {
256        if (scheduleShouldFail)
257            test.fail("Unexpected Throttler.schedule");
258    }
259
260    checkState();
261};
262
263// UI Tests
264
265
266/**
267 * Tests that scripts tab can be open and populated with inspected scripts.
268 */
269TestSuite.prototype.testShowScriptsTab = function()
270{
271    this.showPanel("sources");
272    var test = this;
273    // There should be at least main page script.
274    this._waitUntilScriptsAreParsed(["debugger_test_page.html"],
275        function() {
276            test.releaseControl();
277        });
278    // Wait until all scripts are added to the debugger.
279    this.takeControl();
280};
281
282
283/**
284 * Tests that scripts tab is populated with inspected scripts even if it
285 * hadn't been shown by the moment inspected paged refreshed.
286 * @see http://crbug.com/26312
287 */
288TestSuite.prototype.testScriptsTabIsPopulatedOnInspectedPageRefresh = function()
289{
290    var test = this;
291    this.assertEquals(WebInspector.panels.elements, WebInspector.inspectorView.currentPanel(), "Elements panel should be current one.");
292
293    WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, waitUntilScriptIsParsed);
294
295    // Reload inspected page. It will reset the debugger agent.
296    test.evaluateInConsole_("window.location.reload(true);", function(resultText) {});
297
298    function waitUntilScriptIsParsed()
299    {
300        WebInspector.debuggerModel.removeEventListener(WebInspector.DebuggerModel.Events.GlobalObjectCleared, waitUntilScriptIsParsed);
301        test.showPanel("sources");
302        test._waitUntilScriptsAreParsed(["debugger_test_page.html"],
303            function() {
304                test.releaseControl();
305            });
306    }
307
308    // Wait until all scripts are added to the debugger.
309    this.takeControl();
310};
311
312
313/**
314 * Tests that scripts list contains content scripts.
315 */
316TestSuite.prototype.testContentScriptIsPresent = function()
317{
318    this.showPanel("sources");
319    var test = this;
320
321    test._waitUntilScriptsAreParsed(
322        ["page_with_content_script.html", "simple_content_script.js"],
323        function() {
324          test.releaseControl();
325        });
326
327    // Wait until all scripts are added to the debugger.
328    this.takeControl();
329};
330
331
332/**
333 * Tests that scripts are not duplicaed on Scripts tab switch.
334 */
335TestSuite.prototype.testNoScriptDuplicatesOnPanelSwitch = function()
336{
337    var test = this;
338
339    // There should be two scripts: one for the main page and another
340    // one which is source of console API(see
341    // InjectedScript._ensureCommandLineAPIInstalled).
342    var expectedScriptsCount = 2;
343    var parsedScripts = [];
344
345    this.showPanel("sources");
346
347    function switchToElementsTab() {
348        test.showPanel("elements");
349        setTimeout(switchToScriptsTab, 0);
350    }
351
352    function switchToScriptsTab() {
353        test.showPanel("sources");
354        setTimeout(checkScriptsPanel, 0);
355    }
356
357    function checkScriptsPanel() {
358        test.assertTrue(test._scriptsAreParsed(["debugger_test_page.html"]), "Some scripts are missing.");
359        checkNoDuplicates();
360        test.releaseControl();
361    }
362
363    function checkNoDuplicates() {
364        var uiSourceCodes = test.nonAnonymousUISourceCodes_();
365        for (var i = 0; i < uiSourceCodes.length; i++) {
366            var scriptName = uiSourceCodes[i].url;
367            for (var j = i + 1; j < uiSourceCodes.length; j++)
368                test.assertTrue(scriptName !== uiSourceCodes[j].url, "Found script duplicates: " + test.uiSourceCodesToString_(uiSourceCodes));
369        }
370    }
371
372    test._waitUntilScriptsAreParsed(
373        ["debugger_test_page.html"],
374        function() {
375            checkNoDuplicates();
376            setTimeout(switchToElementsTab, 0);
377        });
378
379
380    // Wait until all scripts are added to the debugger.
381    this.takeControl();
382};
383
384
385// Tests that debugger works correctly if pause event occurs when DevTools
386// frontend is being loaded.
387TestSuite.prototype.testPauseWhenLoadingDevTools = function()
388{
389    this.showPanel("sources");
390
391    // Script execution can already be paused.
392    if (WebInspector.debuggerModel.debuggerPausedDetails)
393        return;
394
395    this._waitForScriptPause(this.releaseControl.bind(this));
396    this.takeControl();
397};
398
399
400// Tests that pressing "Pause" will pause script execution if the script
401// is already running.
402TestSuite.prototype.testPauseWhenScriptIsRunning = function()
403{
404    this.showPanel("sources");
405
406    this.evaluateInConsole_(
407        'setTimeout("handleClick()" , 0)',
408        didEvaluateInConsole.bind(this));
409
410    function didEvaluateInConsole(resultText) {
411        this.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText);
412        // Wait for some time to make sure that inspected page is running the
413        // infinite loop.
414        setTimeout(testScriptPause.bind(this), 300);
415    }
416
417    function testScriptPause() {
418        // The script should be in infinite loop. Click "Pause" button to
419        // pause it and wait for the result.
420        WebInspector.panels.sources._pauseButton.element.click();
421
422        this._waitForScriptPause(this.releaseControl.bind(this));
423    }
424
425    this.takeControl();
426};
427
428
429/**
430 * Tests network size.
431 */
432TestSuite.prototype.testNetworkSize = function()
433{
434    var test = this;
435
436    function finishResource(resource, finishTime)
437    {
438        test.assertEquals(219, resource.transferSize, "Incorrect total encoded data length");
439        test.assertEquals(25, resource.resourceSize, "Incorrect total data length");
440        test.releaseControl();
441    }
442
443    this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishNetworkRequest", finishResource);
444
445    // Reload inspected page to sniff network events
446    test.evaluateInConsole_("window.location.reload(true);", function(resultText) {});
447
448    this.takeControl();
449};
450
451
452/**
453 * Tests network sync size.
454 */
455TestSuite.prototype.testNetworkSyncSize = function()
456{
457    var test = this;
458
459    function finishResource(resource, finishTime)
460    {
461        test.assertEquals(219, resource.transferSize, "Incorrect total encoded data length");
462        test.assertEquals(25, resource.resourceSize, "Incorrect total data length");
463        test.releaseControl();
464    }
465
466    this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishNetworkRequest", finishResource);
467
468    // Send synchronous XHR to sniff network events
469    test.evaluateInConsole_("var xhr = new XMLHttpRequest(); xhr.open(\"GET\", \"chunked\", false); xhr.send(null);", function() {});
470
471    this.takeControl();
472};
473
474
475/**
476 * Tests network raw headers text.
477 */
478TestSuite.prototype.testNetworkRawHeadersText = function()
479{
480    var test = this;
481
482    function finishResource(resource, finishTime)
483    {
484        if (!resource.responseHeadersText)
485            test.fail("Failure: resource does not have response headers text");
486        test.assertEquals(164, resource.responseHeadersText.length, "Incorrect response headers text length");
487        test.releaseControl();
488    }
489
490    this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishNetworkRequest", finishResource);
491
492    // Reload inspected page to sniff network events
493    test.evaluateInConsole_("window.location.reload(true);", function(resultText) {});
494
495    this.takeControl();
496};
497
498
499/**
500 * Tests network timing.
501 */
502TestSuite.prototype.testNetworkTiming = function()
503{
504    var test = this;
505
506    function finishResource(resource, finishTime)
507    {
508        // Setting relaxed expectations to reduce flakiness.
509        // Server sends headers after 100ms, then sends data during another 100ms.
510        // We expect these times to be measured at least as 70ms.
511        test.assertTrue(resource.timing.receiveHeadersEnd - resource.timing.connectStart >= 70,
512                        "Time between receiveHeadersEnd and connectStart should be >=70ms, but was " +
513                        "receiveHeadersEnd=" + resource.timing.receiveHeadersEnd + ", connectStart=" + resource.timing.connectStart + ".");
514        test.assertTrue(resource.responseReceivedTime - resource.startTime >= 0.07,
515                "Time between responseReceivedTime and startTime should be >=0.07s, but was " +
516                "responseReceivedTime=" + resource.responseReceivedTime + ", startTime=" + resource.startTime + ".");
517        test.assertTrue(resource.endTime - resource.startTime >= 0.14,
518                "Time between endTime and startTime should be >=0.14s, but was " +
519                "endtime=" + resource.endTime + ", startTime=" + resource.startTime + ".");
520
521        test.releaseControl();
522    }
523
524    this.addSniffer(WebInspector.NetworkDispatcher.prototype, "_finishNetworkRequest", finishResource);
525
526    // Reload inspected page to sniff network events
527    test.evaluateInConsole_("window.location.reload(true);", function(resultText) {});
528
529    this.takeControl();
530};
531
532
533TestSuite.prototype.testConsoleOnNavigateBack = function()
534{
535    if (WebInspector.multitargetConsoleModel.messages().length === 1)
536        firstConsoleMessageReceived.call(this);
537    else
538        WebInspector.multitargetConsoleModel.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, firstConsoleMessageReceived, this);
539
540    function firstConsoleMessageReceived() {
541        WebInspector.multitargetConsoleModel.removeEventListener(WebInspector.ConsoleModel.Events.MessageAdded, firstConsoleMessageReceived, this);
542        this.evaluateInConsole_("clickLink();", didClickLink.bind(this));
543    }
544
545    function didClickLink() {
546        // Check that there are no new messages(command is not a message).
547        this.assertEquals(3, WebInspector.multitargetConsoleModel.messages().length);
548        this.evaluateInConsole_("history.back();", didNavigateBack.bind(this));
549    }
550
551    function didNavigateBack()
552    {
553        // Make sure navigation completed and possible console messages were pushed.
554        this.evaluateInConsole_("void 0;", didCompleteNavigation.bind(this));
555    }
556
557    function didCompleteNavigation() {
558        this.assertEquals(7, WebInspector.multitargetConsoleModel.messages().length);
559        this.releaseControl();
560    }
561
562    this.takeControl();
563};
564
565TestSuite.prototype.testReattachAfterCrash = function()
566{
567    PageAgent.navigate("about:crash");
568    PageAgent.navigate("about:blank");
569    WebInspector.runtimeModel.addEventListener(WebInspector.RuntimeModel.Events.ExecutionContextCreated, this.releaseControl, this);
570};
571
572
573TestSuite.prototype.testSharedWorker = function()
574{
575    function didEvaluateInConsole(resultText) {
576        this.assertEquals("2011", resultText);
577        this.releaseControl();
578    }
579    this.evaluateInConsole_("globalVar", didEvaluateInConsole.bind(this));
580    this.takeControl();
581};
582
583
584TestSuite.prototype.testPauseInSharedWorkerInitialization = function()
585{
586    if (WebInspector.debuggerModel.debuggerPausedDetails)
587        return;
588    this._waitForScriptPause(this.releaseControl.bind(this));
589    this.takeControl();
590};
591
592TestSuite.prototype.enableTouchEmulation = function()
593{
594    WebInspector.targetManager.mainTarget().domModel.emulateTouchEventObjects(true);
595};
596
597
598// Regression test for crbug.com/370035.
599TestSuite.prototype.testDeviceMetricsOverrides = function()
600{
601    const dumpPageMetrics = function()
602    {
603        return JSON.stringify({
604            width: window.innerWidth,
605            height: window.innerHeight,
606            deviceScaleFactor: window.devicePixelRatio
607        });
608    };
609
610    var test = this;
611
612    function testOverrides(params, metrics, callback)
613    {
614        PageAgent.invoke_setDeviceMetricsOverride(params, getMetrics);
615
616        function getMetrics()
617        {
618            test.evaluateInConsole_("(" + dumpPageMetrics.toString() + ")()", checkMetrics);
619        }
620
621        function checkMetrics(consoleResult)
622        {
623            test.assertEquals('"' + JSON.stringify(metrics) + '"', consoleResult, "Wrong metrics for params: " + JSON.stringify(params));
624            callback();
625        }
626    }
627
628    function step1()
629    {
630        testOverrides({width: 1200, height: 1000, deviceScaleFactor: 1, mobile: false, fitWindow: true}, {width: 1200, height: 1000, deviceScaleFactor: 1}, step2);
631    }
632
633    function step2()
634    {
635        testOverrides({width: 1200, height: 1000, deviceScaleFactor: 1, mobile: false, fitWindow: false}, {width: 1200, height: 1000, deviceScaleFactor: 1}, step3);
636    }
637
638    function step3()
639    {
640        testOverrides({width: 1200, height: 1000, deviceScaleFactor: 3, mobile: false, fitWindow: true}, {width: 1200, height: 1000, deviceScaleFactor: 3}, step4);
641    }
642
643    function step4()
644    {
645        testOverrides({width: 1200, height: 1000, deviceScaleFactor: 3, mobile: false, fitWindow: false}, {width: 1200, height: 1000, deviceScaleFactor: 3}, finish);
646    }
647
648    function finish()
649    {
650        test.releaseControl();
651    }
652
653    WebInspector.overridesSupport._deviceMetricsChangedListenerMuted = true;
654    test.takeControl();
655    this.waitForThrottler(WebInspector.overridesSupport._deviceMetricsThrottler, step1);
656};
657
658TestSuite.prototype.waitForTestResultsInConsole = function()
659{
660    var messages = WebInspector.multitargetConsoleModel.messages();
661    for (var i = 0; i < messages.length; ++i) {
662        var text = messages[i].messageText;
663        if (text === "PASS")
664            return;
665        else if (/^FAIL/.test(text))
666            this.fail(text); // This will throw.
667    }
668    // Neither PASS nor FAIL, so wait for more messages.
669    function onConsoleMessage(event)
670    {
671        var text = event.data.messageText;
672        if (text === "PASS")
673            this.releaseControl();
674        else if (/^FAIL/.test(text))
675            this.fail(text);
676    }
677
678    WebInspector.multitargetConsoleModel.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, onConsoleMessage, this);
679    this.takeControl();
680};
681
682TestSuite.prototype.checkLogAndErrorMessages = function()
683{
684    var messages = WebInspector.multitargetConsoleModel.messages();
685
686    var matchesCount = 0;
687    function validMessage(message)
688    {
689        if (message.text === "log" && message.level === WebInspector.ConsoleMessage.MessageLevel.Log) {
690            ++matchesCount;
691            return true;
692        }
693
694        if (message.text === "error" && message.level === WebInspector.ConsoleMessage.MessageLevel.Error) {
695            ++matchesCount;
696            return true;
697        }
698        return false;
699    }
700
701    for (var i = 0; i < messages.length; ++i) {
702        if (validMessage(messages[i]))
703            continue;
704        this.fail(messages[i].text + ":" + messages[i].level); // This will throw.
705    }
706
707    if (matchesCount === 2)
708        return;
709
710    // Wait for more messages.
711    function onConsoleMessage(event)
712    {
713        var message = event.data;
714        if (validMessage(message)) {
715            if (matchesCount === 2) {
716                this.releaseControl();
717                return;
718            }
719        } else
720            this.fail(message.text + ":" + messages[i].level);
721    }
722
723    WebInspector.multitargetConsoleModel.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, onConsoleMessage, this);
724    this.takeControl();
725};
726
727/**
728 * Serializes array of uiSourceCodes to string.
729 * @param {!Array.<!WebInspectorUISourceCode>} uiSourceCodes
730 * @return {string}
731 */
732TestSuite.prototype.uiSourceCodesToString_ = function(uiSourceCodes)
733{
734    var names = [];
735    for (var i = 0; i < uiSourceCodes.length; i++)
736        names.push('"' + uiSourceCodes[i].url + '"');
737    return names.join(",");
738};
739
740
741/**
742 * Returns all loaded non anonymous uiSourceCodes.
743 * @return {!Array.<!WebInspectorUISourceCode>}
744 */
745TestSuite.prototype.nonAnonymousUISourceCodes_ = function()
746{
747    function filterOutAnonymous(uiSourceCode)
748    {
749        return !!uiSourceCode.url;
750    }
751
752    function filterOutService(uiSourceCode)
753    {
754        return !uiSourceCode.project().isServiceProject();
755    }
756
757    var uiSourceCodes = WebInspector.workspace.uiSourceCodes();
758    uiSourceCodes = uiSourceCodes.filter(filterOutService);
759    return uiSourceCodes.filter(filterOutAnonymous);
760};
761
762
763/*
764 * Evaluates the code in the console as if user typed it manually and invokes
765 * the callback when the result message is received and added to the console.
766 * @param {string} code
767 * @param {function(string)} callback
768 */
769TestSuite.prototype.evaluateInConsole_ = function(code, callback)
770{
771    function innerEvaluate()
772    {
773        WebInspector.console.show();
774        var consoleView = WebInspector.ConsolePanel._view();
775        consoleView._prompt.text = code;
776        consoleView._promptElement.dispatchEvent(TestSuite.createKeyEvent("Enter"));
777
778        this.addSniffer(WebInspector.ConsoleView.prototype, "_showConsoleMessage",
779            function(viewMessage) {
780                callback(viewMessage.toMessageElement().textContent);
781            }.bind(this));
782    }
783
784    if (!WebInspector.context.flavor(WebInspector.ExecutionContext)) {
785        WebInspector.context.addFlavorChangeListener(WebInspector.ExecutionContext, innerEvaluate, this);
786        return;
787    }
788
789    innerEvaluate.call(this);
790};
791
792/**
793 * Checks that all expected scripts are present in the scripts list
794 * in the Scripts panel.
795 * @param {!Array.<string>} expected Regular expressions describing
796 *     expected script names.
797 * @return {boolean} Whether all the scripts are in "scripts-files" select
798 *     box
799 */
800TestSuite.prototype._scriptsAreParsed = function(expected)
801{
802    var uiSourceCodes = this.nonAnonymousUISourceCodes_();
803    // Check that at least all the expected scripts are present.
804    var missing = expected.slice(0);
805    for (var i = 0; i < uiSourceCodes.length; ++i) {
806        for (var j = 0; j < missing.length; ++j) {
807            if (uiSourceCodes[i].name().search(missing[j]) !== -1) {
808                missing.splice(j, 1);
809                break;
810            }
811        }
812    }
813    return missing.length === 0;
814};
815
816
817/**
818 * Waits for script pause, checks expectations, and invokes the callback.
819 * @param {function():void} callback
820 */
821TestSuite.prototype._waitForScriptPause = function(callback)
822{
823    function pauseListener(event) {
824        WebInspector.debuggerModel.removeEventListener(WebInspector.DebuggerModel.Events.DebuggerPaused, pauseListener, this);
825        callback();
826    }
827    WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerPaused, pauseListener, this);
828};
829
830
831/**
832 * Waits until all the scripts are parsed and asynchronously executes the code
833 * in the inspected page.
834 */
835TestSuite.prototype._executeCodeWhenScriptsAreParsed = function(code, expectedScripts)
836{
837    var test = this;
838
839    function executeFunctionInInspectedPage() {
840        // Since breakpoints are ignored in evals' calculate() function is
841        // execute after zero-timeout so that the breakpoint is hit.
842        test.evaluateInConsole_(
843            'setTimeout("' + code + '" , 0)',
844            function(resultText) {
845                test.assertTrue(!isNaN(resultText), "Failed to get timer id: " + resultText + ". Code: " + code);
846            });
847    }
848
849    test._waitUntilScriptsAreParsed(expectedScripts, executeFunctionInInspectedPage);
850};
851
852
853/**
854 * Waits until all the scripts are parsed and invokes the callback.
855 */
856TestSuite.prototype._waitUntilScriptsAreParsed = function(expectedScripts, callback)
857{
858    var test = this;
859
860    function waitForAllScripts() {
861        if (test._scriptsAreParsed(expectedScripts))
862            callback();
863        else
864            test.addSniffer(WebInspector.panels.sources.sourcesView(), "_addUISourceCode", waitForAllScripts);
865    }
866
867    waitForAllScripts();
868};
869
870
871/**
872 * Key event with given key identifier.
873 */
874TestSuite.createKeyEvent = function(keyIdentifier)
875{
876    var evt = document.createEvent("KeyboardEvent");
877    evt.initKeyboardEvent("keydown", true /* can bubble */, true /* can cancel */, null /* view */, keyIdentifier, "");
878    return evt;
879};
880
881
882/**
883 * Test runner for the test suite.
884 */
885var uiTests = {};
886
887
888/**
889 * Run each test from the test suit on a fresh instance of the suite.
890 */
891uiTests.runAllTests = function()
892{
893    // For debugging purposes.
894    for (var name in TestSuite.prototype) {
895        if (name.substring(0, 4) === "test" && typeof TestSuite.prototype[name] === "function")
896            uiTests.runTest(name);
897    }
898};
899
900
901/**
902 * Run specified test on a fresh instance of the test suite.
903 * @param {string} name Name of a test method from TestSuite class.
904 */
905uiTests.runTest = function(name)
906{
907    if (uiTests._populatedInterface)
908        new TestSuite().runTest(name);
909    else
910        uiTests._pendingTestName = name;
911};
912
913(function() {
914
915function runTests()
916{
917    uiTests._populatedInterface = true;
918    var name = uiTests._pendingTestName;
919    delete uiTests._pendingTestName;
920    if (name)
921        new TestSuite().runTest(name);
922}
923
924WebInspector.notifications.addEventListener(WebInspector.NotificationService.Events.InspectorAgentEnabledForTests, runTests);
925
926})();
927
928}
929