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